# Prepr Documentation - Full Export
> Complete Prepr CMS documentation in one file.
---
# Start building with Prepr
This is the place to learn all about Prepr. Explore the topics below to start learning or create your [free account here](https://signup.prepr.io) if you haven't done so already.
If you need any help, don't hesitate to reach out:
[Join our Slack](https://slack.prepr.io) or
[Reach out to our support team](https://prepr.io/support).
## Connect a front-end framework
## More resources
Source: https://docs.prepr.io/index
---
# Quick start guide
*Estimated duration: 15-30 minutes*
## Introduction
Prepr allows you to develop a dynamic, data-driven, web application with ease. This tutorial explains how to get started with Prepr. We will cover all the basics: modeling content types, adding content and images and retrieving content using the API. Follow the step-by-step guide below.
## Use case
This tutorial explains how to manage articles and author profiles in Prepr and retrieve that content using the GraphQL API. For example, to create a blog. You can apply these principles to all kinds of situations.

## Step 1: Create an environment
If you haven't already done so, go to https://signup.prepr.io/ and sign up for a Prepr account.
1. After you sign up, you will be prompted to add an environment. Enter a **Name** and choose the **Default locale** and **Development stage**.

2. On the next screen, you will be prompted to either load demo data or to start from scratch.
While importing demo data can help you explore Prepr features with preset schema and content items, for the purpose of this tutorial we recommend starting from scratch to experience the whole setup process yourself. To get a clean environment, click **Start from scratch**.

That’s it. You’ve created your first environment in Prepr. In the next step, you can add models and fields for our blog use case.
## Step 2: Create content models
A headless CMS allows you to define a structure for your content according to your needs. We call that content modeling. For this example, you need an **Article** model and a **Person** model.

Let’s create these models in Prepr:
1. Click the **Schema** tab to open the *Schema Editor*.
2. In the *Models* section, click the **+ Add model** link.
3. Choose **Multi-item model** and enter *Person* in the **Display name** field and click **Next** and **Save**. Check out the [Models doc](/content-modeling/managing-models#manage-settings) for more details on additional options.

5. Drag and drop the **Text** field type, from the list on the right into your model.
a. Enter *Name* in the **Display name** field and click **Save**.
6. Drag and drop another **Text** field type, from the list on the right into your model.
a. Enter *Bio* in the **Display name** field.
b. Click the **Settings** tab.
c. Select *Text area* and click **Save**.
7. Drag and drop the **Assets** field type, from the list on the right into your model.
a. Choose the **Multi-asset field** option.
b. Enter *Image* in the **Display name** and click **Save**.
Your Person model should now look like this:

Now you can add the Article model.
1. Click the **+ Add model** button on the left.
2. Choose the **Multi-item model** option, enter *Article* in the **Display name** field, click **Next** and **Save**.
Check out the [Models doc](/content-modeling/managing-models#manage-settings) for more details on additional options.
Your model should look something like this:

And add the fields:
4. Drag and drop the **Text** field type, from the list on the right into your model.
a. Enter *Headline* in the **Display name** field and click **Save**.
5. Drag and drop another **Slug** field type, from the list on the right into your model.
a. Click **headline** in the box below to fill the Slug template with `{headline}`.
b. Click **Save**.
Now we’re going to add the author field. This is not a text field, but a reference to the Person model you created earlier.
A reference field allows you to link to other content items, as in this case to a Person.
6. Drag and drop the **Content reference** field type, from the list on the right into your model.
a. Enter *Author* in the **Display name** field.
b. Select the *Person* model and click **Save**.

7. Next, add the rest of the fields below in the same way as described above:
- *Intro* - **Text** field
- *Image* - **Assets** field
- *Content* - **Dynamic content** field
- *Tags* - **Tags** field
See an overview of [all field types](/content-modeling/field-types). Your model should look like the image below.

## Step 3: Create content items
Now that you've created the models, you can add content items. Start by adding a *Person*:
1. Go to the **Content** tab, click the **Add item** button and choose **Person**.
2. Fill out the **Name** and **Bio** fields.
3. Drag and drop an image into the **Image** field or click it to add an image from your local storage.

6. Click the **Publish** dropdown and select the **Publish and close** option.
Now, add an article:
1. Click the **Add item** button again.
2. This time, select **Article**.
3. Enter a **Headline**. You will notice the slug field automatically being populated. Copy this slug value to retrieve your article in [Step 5](#step-5-retrieve-a-single-article-from-the-api).
4. Click the **+ Person** link to choose and add a person to the *Author* field.

Now fill in the remaining fields.
6. Add some text and images to the *Content* field.
7. Add some tags in the *Tags* field. Add tags by selecting an existing tag in the list, or adding a new tag and clicking **Enter**.
8. Click the **Publish** dropdown and select the **Publish and close** option.
The next step is to retrieve the content items from the API so you can display the content in your web application.
## Step 4: Retrieve articles from the API
The easiest way to experience content retrieval through the API is to use the API explorer:
1. Click the icon and choose the **Access tokens** option.
2. Click to open the *GraphQL Production* token details.
3. Open the API explorer by clicking the **Open API Explorer** link.

Make sure you see a green dot and your access token at the end of the url. If you cannot connect, please contact support.

With the explorer, you can easily create and test API queries. Let’s create a query to retrieve a list of all articles:
4. Add the following query
```graphql copy
query {
Articles {
items {
_id
headline
}
}
}
```
Note that you can retrieve the ID for all content items using the system field *\_id*. You can recognize system fields by the underscore in front of the field name. Check out [all system fields](/graphql-api/schema-system-fields) for more information.
5. Click **Run** to execute the query
The result should look something like this:
```json copy
{
"data": {
"Articles": {
"items": [
{
"_id": "4fcad70d-2beb-4886-bd8d-1753020bf315",
"headline": "How to get the best deals : Insider tips and tricks"
}
]
}
}
}
```
As you can see, the response includes a list of all the items. At the moment, there is only one article, which is listed including its ID and headline. Let's expand the query so that you retrieve the other fields as well.
```graphql copy
query {
Articles(
where: {
_publish_on_gt : "2025-06-15T09:00:00+00:00"
}
)
{
items {
_id
_slug
_publish_on
author {
_id
name
image {
url(width:800)
}
}
headline
image {
url(width: 1000)
}
intro
tags {
body
}
}
}
}
}
```
In the query above, we added a condition to get all articles published on or after June 15th, 2025. This query includes the following fields that we want to see in the result:
- *\_slug* field
- *\_publish\_on* field to retrieve the publication date
- *author* field to retrieve reference fields ([learn more](/graphql-api/schema-field-types#content-reference))
- *image* field to retrieve assets ([learn more](/graphql-api/schema-field-types#assets))
- *content* field to retrieve a dynamic content field ([learn more](/graphql-api/schema-field-types-dynamic-content-field))
- *tags* field to retrieve tags ([learn more](/graphql-api/schema-field-types#tag))
Check out the documentation for [all field types](/graphql-api/schema-field-types) and how to retrieve [multiple content items](/graphql-api/fetching-collections) for more information.
- Run the query.
The response should look something like this:
```json copy
{
"data": {
"Articles": {
"items": [
{
"_id": "38a05b5c-98cf-4275-8d2b-3748f49cc104",
"_slug": "how-to-get-the-best-deals-insider-tips-and-tricks",
"_publish_on": "2025-10-07T12:08:00+00:00",
"author": [
{
"_id": "dee58849-bb0a-4ceb-a288-7adc9e623602",
"name": "Emma Carter",
"image": {
"url": "https://ml98.stream.prepr.io/6iku17cg8lc8/w_800/emma-carter-profile.jpg"
}
}
],
"headline": "How to get the best deals : Insider tips and tricks",
"image": {
"url": "https://ml98.stream.prepr.io/6i57ud05jnjq/w_1000/business-deal-photo.jpg"
},
"intro": "Want to lease a car without overpaying? Follow these expert tips to negotiate the best lease deal, avoid common pitfalls, and maximize your savings.",
"tags": [
{
"body": "car deals"
},
{
"body": "tips and tricks"
}
]
}
]
}
}
}
```
And voila, a JSON response containing all the data from your content items.
We just queried a list of articles that you can use to show on a blog overview page. On that page, you give each item a unique URL based on the slug—for example:
```
https://yourdomain.com/articles/how-to-get-the-best-deals-insider-tips-and-tricks
```
## Step 5: Retrieve a single article from the API
You want to show the whole article when a visitor clicks the link. Retrieving a single content item can be done using the below query. Replace the slug value with the value that you copied in [Step 3](#step-3-create-content-items).
```graphql copy
query {
Article( slug: "how-to-get-the-best-deals-insider-tips-and-tricks") {
_id
headline
}
}
```
In the query above, we specify a condition to return an item with the matching slug. Update the slug value in quotes to match the slug of the article that you created.
When you run this query you get the following result:
```json copy
{
"data": {
"Article": {
"_id": "38a05b5c-98cf-4275-8d2b-3748f49cc104",
"headline": "How to get the best deals : Insider tips and tricks"
}
}
}
```
The last thing you need to know is how to retrieve the article content. Expand the query like this:
```graphql copy
query {
Article(slug: "how-to-get-the-best-deals-insider-tips-and-tricks") {
_id
headline
author {
_id
name
image {
url(width:800)
}
}
intro
image {
url(width: 1000)
}
content {
... on Text {
format
body
}
... on Assets {
items {
url(width:600)
}
}
}
tags {
body
}
}
}
```
You've already seen most of the fields when you retrieved the article list. The content field is new. The preceding query shows how to retrieve a dynamic content field. Per element type, you fetch the information. In this case, for text elements and assets only, however, there are many more element types. Check out the [dynamic content field documentation](/graphql-api/schema-field-types-dynamic-content-field) for all the details.
Here’s the result:
```json copy
{
"data": {
"Article": {
"_id": "38a05b5c-98cf-4275-8d2b-3748f49cc104",
"headline": "How to get the best deals : Insider tips and tricks",
"author": [
{
"_id": "dee58849-bb0a-4ceb-a288-7adc9e623602",
"name": "Emma Carter",
"image": {
"url": "https://ml98.stream.prepr.io/6iku17cg8lc8/w_800/emma-carter-profile.jpg"
}
}
],
"intro": "Want to lease a car without overpaying? Follow these expert tips to negotiate the best lease deal, avoid common pitfalls, and maximize your savings.",
"image": {
"url": "https://ml98.stream.prepr.io/6i57ud05jnjq/w_1000/business-deal-photo.jpg"
},
"content": [
{
"format": null,
"body": "
Leasing a car can be a great way to drive a new vehicle without the high upfront cost of purchasing. However, not all lease deals are created equal. If you want to save money and get the most value , follow these expert tips to secure the best lease deal possible .
"
},
{
"format": null,
"body": "1. Understand the Key Lease Terms
"
},
{
"format": null,
"body": "Before negotiating, it’s essential to understand how leases work . Here are the critical terms you should know:
"
},
{
"format": null,
"body": "• Capitalized Cost (Cap Cost) – The vehicle’s price before lease calculations. Like when buying, this can be negotiated down.
"
},
{
"format": null,
"body": "• Residual Value – The car’s estimated value at the end of the lease. Higher residual values mean lower monthly payments .
"
},
{
"format": null,
"body": "• Money Factor – Equivalent to the interest rate on your lease. The lower, the better.
"
},
{
"format": null,
"body": "• Mileage Limit – Most leases have annual limits (e.g., 10,000–15,000 miles) . Exceeding this incurs extra charges.
"
},
{
"format": null,
"body": "2. Negotiate the Capitalized Cost
"
},
{
"format": null,
"body": "Many people assume lease prices are fixed —they’re not! You can negotiate the cap cost just like when buying a car . Research the market price of the vehicle and ask for a discount. The lower the price, the lower your monthly lease payments .
"
},
{
"format": null,
"body": "3. Look for Manufacturer Lease Specials
"
},
{
"format": null,
"body": "Car manufacturers frequently offer special lease deals , reducing monthly payments, down payments, or interest rates. These can include:
"
},
{
"format": null,
"body": "• Cash rebates
"
},
{
"format": null,
"body": "• Loyalty discounts
"
},
{
"format": null,
"body": "• Zero down-payment leases
"
},
{
"format": null,
"body": "Check official websites and dealership promotions for these offers.
"
},
{
"format": null,
"body": "4. Choose a Car with a High Residual Value
"
},
{
"format": null,
"body": "Cars that retain their value well will have a higher residual value , leading to lower monthly payments. Brands like Toyota, Honda, and Lexus typically have strong resale values.
"
},
{
"format": null,
"body": "5. Understand Lease Fees and Extra Costs
"
},
{
"format": null,
"body": "Be aware of the following potential extra costs :
"
},
{
"format": null,
"body": "• Acquisition Fee – A fee for setting up the lease.
"
},
{
"format": null,
"body": "• Disposition Fee – Charged at the end of the lease if you return the car.
"
},
{
"format": null,
"body": "• Excess Mileage Charges – Avoid these by choosing a lease with the right mileage limit.
"
},
{
"format": null,
"body": "6. Consider a One-Pay Lease
"
},
{
"format": null,
"body": "Instead of making monthly payments, some dealerships offer a single lump-sum payment lease . If you can afford it, this may save you thousands in interest fees.
"
},
{
"format": null,
"body": "7. Final Tip: Read the Lease Agreement Carefully
"
},
{
"format": null,
"body": "Before signing, review every detail of the lease to avoid hidden costs . If anything is unclear, ask for clarification.
"
}
],
"tags": [
{
"body": "car deals"
},
{
"body": "tips and tricks"
}
]
}
}
}
```
For more details check out the [fetching single content items](/graphql-api/fetching-single-items) API reference.
Congratulations, you have completed your first steps with Prepr. From modeling and creating content items to retrieving content using the API. With that, you have mastered the basics of Prepr.
## Step 6: Connect your front end
The next step is to connect your web application. We have guides for all major front-end frameworks to quickly get you up and running.
- [Next.js](/connecting-a-front-end-framework/nextjs)
- [Nuxt](/connecting-a-front-end-framework/nuxtjs)
- [Laravel](/connecting-a-front-end-framework/laravel)
- [Angular](/connecting-a-front-end-framework/angular)
- [Node.js](/connecting-a-front-end-framework/nodejs)
- [PHP](/connecting-a-front-end-framework/php)
- [React](/connecting-a-front-end-framework/react)
- [Vue.js](/connecting-a-front-end-framework/vuejs)
## Want to learn more?
Of course, there is much more to explore. For example, with Prepr, you can perform A/B testing, present recommendations and personalize the visitor journey.
For more advanced topics, please refer to the rest of the documentation, or contact one of our specialists.
Check out the following chapters:
- [A/B testing](/ab-testing)
- [Personalization](/personalization)
- [Recommendations](/recommendations)
## Schedule a free consultation
Do you want to get started but still have questions or want a demo?
[Schedule a free call](https://prepr.io/get-a-demo) with a Prepr solution engineer.
During the consultation we can provide recommendations on topics like:
- Content modeling for the desired use case
- Working with multiple environments, websites and languages
- Creating personalized experiences for your website visitors
- A/B testing and other optimization strategies
Source: https://docs.prepr.io/quick-start-guide
---
# Changelog
Beautiful new features and important updates are added to Prepr on a daily basis. This changelog gives you an insight into the most eye-catching releases. Be aware that updates can be rolled out in phases so they may not always be available in all Prepr environments at the same time.
## Introducing content metrics
With the new content item metrics you can easily see the impact of your content directly in the content item list.

When diving deeper, you can get more detailed metrics including the reach, engagement and performance for a specific content item.
This means you get insights into what works to make improvements and focus on the content that works.
Check out the [content metrics guide](/content-management/measuring-content-impact) for more details.
## Improved access token management
We've just made the access token page clutter-free.
Now, you’ll only see the information relevant to the specific type of token you're viewing.
No more endless scrolling to see the details you need when managing access tokens.

Plus, when creating a token, you can now choose the exact type you need: *GraphQL API*, *REST API*, or *Customer API*.
Check out the [REST API token example](/mutation-api/authorization) for more details.
## Copy and paste Stack field elements
You can now copy and paste components in *Stack* fields, including all values entered in the component.

This makes it easier to quickly reuse existing components across different stack fields without having to configure them from scratch.
The *Stack* field interface now also provides a more streamlined way to add models and components, with improved behavior for narrow views and a cleaner empty state.
Check out the [stack field guide](/content-management/managing-content/creating-rich-content#working-with-a-stack-field) for more details.
## OneLogin integration now available for SSO
You can now integrate OneLogin to Prepr to set up single sign-on (SSO) for your Prepr users using this cloud-based Identity and Access Management (IAM) solution.

By activating this integration, you enhance security and give your OneLogin users an improved login experience.
Check out the [SSO guide](/project-setup/setting-up-sso#onelogin) for more details.
## Prefilled AI prompts for segment and environment context
We’ve added prefilled AI context prompts directly inside the *Segment* and *Environment* settings.
You can now launch ChatGPT, Claude, or Gemini with one click to generate complete context profiles without writing prompts from scratch.

Create relevant AI context in seconds for every audience.
Check out the [segment settings](/personalization/managing-segments#defining-segment-context) and [environment settings](/project-setup/setting-up-environments#define-environment-context-for-ai-requests) for more details.
## Introducing the Bynder enterprise DAM integration
You can now connect Bynder to Prepr so content editors can use assets from this enterprise DAM platform directly in content items.

This gives teams a single source for assets while keeping content production inside Prepr.
Check out the [Bynder integration guide](/integrations/bynder) for more details.
## Introducing the framework-independent Prepr toolkit (Beta)
Whether you’re building with Next.js, Astro, React, Nuxt.js, or Svelte, you can now automate front-end features like the preview, edit-mode and experiments with the new Prepr toolkit. This is a lightweight, high-performance toolkit based on JavaScript standards.
If you’re currently using the legacy @prepr/nextjs package, we strongly recommend upgrading to the new Prepr toolkit to take advantage of its broader framework support and streamlined approach to integrating Prepr features. Follow the [migration guide](https://github.com/preprio/prepr-toolkit/blob/main/MIGRATION.md) to upgrade.
Check out the [GitHub repo](https://github.com/preprio/prepr-toolkit) for more details and example projects.
## Faster content item loading for complex content structures
We’ve significantly improved the performance of opening content items in the content editor, especially for content items with many language variants, components, or references.
**What’s improved?**
- Language variants now load on demand, instead of loading all locales upfront.
- Stack field content is loaded more efficiently, reducing the amount of data processed when opening an item.
**Impact**
Customers with complex content structures may experience dramatically faster load times when opening content items. In some cases, we’ve measured up to **16x faster performance**.
This improvement is particularly noticeable for:
- Content items with many locales/translations.
- Content items containing stack fields with numerous components and references.
The result is a more responsive editing experience and reduced waiting time for content teams working with complex content models.
Check out a [stack field example](/content-modeling/examples/page) for more details.
## Protected asset deletion
To protect your content integrity, Prepr now prevents you from deleting assets that are currently in use by a content item.
Previously, deleting an in-use asset only triggered a warning.

This change prevents broken websites, missing media, and downstream application errors caused by deleted assets.
Check out the [asset management guide](/content-management/managing-assets/managing-assets#deleting-assets) for more details.
## Legacy API endpoint is now retired
As previously announced, the legacy API endpoint `api.eu1.prepr.io` has been officially retired today and is no longer operational.
Please confirm all requests have been migrated to the updated endpoints:
- Write operations (mutations): `mutation.prepr.io`
- Read-only operations (queries): `cdn.prepr.io`
Other Prepr domains (such as `customers.prepr.io`) remain unaffected. This change only impacts the domain being called and does not alter your data or underlying application logic.
If you experience any unexpected API errors or need assistance finalizing your migration, please reach out to [Prepr Support](mailto:support@prepr.io) immediately.
## Introducing the Leadinfo integration
With the new integration to this B2B lead generation software, you can segment website visitors based on their industry and company size.

This allows you to personalize content for an enhanced user experience for your B2B audience.
Check out the [Leadinfo integration guide](/integrations/leadinfo) for more details.
## MCP server: Expanded media support and schema settings
We introduced key enhancements to the schema context for AI agents, expanded the media capabilities, and resolved some issues.
For the schema context, the MCP server exposes additional field-level metadata:
- Default values
- Min/max field constraints
- Complete enum option lists
For media support, you can now upload all asset types (images, documents, audio, and video) up to 10 GB.
For more details, checkout the [MCP server docs](/prepr-mcp-server/release-notes#added-schema-settings-exposed-to-the-llm).
## GraphQL responses now 25% faster for complex schemas
We optimized schema initialization for environments with large and complex content models.
This reduces request overhead and improves response-time consistency, benchmarks showing over 25% faster responses.
Check out the full [GraphQL API reference](/graphql-api).
## GitHub schema syncs are now up to 25× faster
We’ve optimized GitHub schema downloads by processing files concurrently.
Schema validation, imports, and dry runs now complete significantly faster, especially for schemas containing many models and components.
For more details on the GitHub schema sync process, check out the [Syncing a schema guide](/development/working-with-cicd/syncing-a-schema#github-schema-sync).
## New retranslate option for content items
The new **Retranslate** option lets you instantly replace the language variant text in a content item from a source language.
For example, when the source language variant has a lot of changes since the first translation.

This means you save time and maintain consistency with a single click of a button instead of using workarounds or manually translating.
Check out the [localizing content guide](/content-management/localizing-content#retranslate-a-language-variant) for more details.
## Duplicating content items
You can now duplicate a content item directly in the content item detail page, allowing you to create multiple similar content items with the same components like landing pages.

This means you can focus purely on updating the copy and visuals.
Check out the [content management guide](/content-management/managing-content/managing-content-items#duplicate-a-content-item) for more details.
## Improved AI text assistant for better results
We've improved our AI features for creating content. Now you can,
- use AI to improve text in the dynamic content editor
- use your own prompts to improve text
- continue prompting to fine-tune your initial request

This update creates a more consistent AI experience so you get better results and speed up your editing workflow.
In other words, spend less time editing and more time publishing.
Check out the [AI text assistant guide](/ai-text-assistant) for more details.
## Environment-scoped preview URLs in local schemas
You can now assign preview URLs to specific environments directly in local schemas.

This makes managing preview URLs across your DTAP workflow much easier.
For example, configure all environment-specific preview URLs in Development, sync your schema, and the environment assignments remain intact, no need to reconfigure preview URLs after syncing to Production.
Check out the [model settings](/content-modeling/managing-models#preview) for more details.
## New remote source spec to validate your custom endpoint
We’ve just released a [new spec for custom remote sources](https://github.com/preprio/remote-source-validation).
Now, you can automatically validate your custom endpoint based on its response format.
This way you make sure you release a Prepr-compatible custom endpoint with fewer mistakes.
For more details, check out the [custom remote source guide](/content-modeling/creating-a-custom-remote-source#validate-the-custom-remote-source).
## Improved subscription page gives you more clarity on usage
With the improved subscription page, you can easily view your usage at a glance and enable notifications when your usage hits a threshold.

This gives you better visibility and control for total peace of mind.
For more details, check out the [subscription guide](/project-setup/managing-your-subscription).
## New tracking code fallback to record view events
Previously, the Prepr tracking code only recorded view events by identifying content items with the specific `id` meta tag.
Now, it also automatically records view events by matching the URL path (slug) to the correct content item when the meta tag is not present.
This update prevents data gaps caused by accidental omission of the `id` meta tags in your front-end code, ensuring your analytics remain complete.
Check out the [recording events guide](/data-collection/recording-events#view) for more details.
## Prepr MCP server public beta now live
Thanks to our users who've tried out the closed beta release of the Prepr MCP server we've made improvements and can provide [real-life use cases](/prepr-mcp-server/use-cases).
Here are some of the enhancements:
- Migrated server domain to `https://mcp.prepr.io`
- Added OAuth support for secure agent connection
- AI agent scopes mirror your authenticated user role permissions
For a full list of updates since the closed beta, checkout the [release notes](/prepr-mcp-server/release-notes).
To get started and connect your own AI agent, check out the [full MCP docs](/prepr-mcp-server).
## Case-insensitive personalization for UTM values
We’ve improved how UTM values are matched in segment conditions.
Previously, UTM values required an exact case match. For example, a condition targeting SpringCampaign would not match springCampaign. Now, UTM values are matched case-insensitively, making visitor segmentation and personalization more intuitive.
This improvement helps prevent unexpected results caused by inconsistent UTM capitalization across campaigns and marketing tools.
## Password leak detection across authentication flows
We've implemented leaked credential detection across all key authentication flows:
- Sign-in
- Password reset
- Password creation after profile invite
During these processes credentials are validated against breach intelligence, and users are required to reset their password if a compromised password is detected.

This improvement strengthens account security by preventing the use of exposed credentials that are known from external data breaches. By proactively detecting and blocking leaked passwords, we reduce the risk of credential stuffing attacks and unauthorized account access, significantly improving protection for user accounts and overall platform security.
Check out [this blog post](https://blog.cloudflare.com/helping-keep-customers-safe-with-leaked-password-notification/) for more details.
## Introducing the Jotform integration
With the new integration to this online form and surveys platform, you can embed Jotform forms directly into your content items.

This means you keep your content and related Jotform forms in one place.
Check out the [Jotform integration guide](/integrations/jotform) for more details.
## Introducing the ActiveCampaign integration
With the new integration to this email marketing platform, you can embed ActiveCampaign forms directly into your content items.

This means you keep your content and related ActiveCampaign forms in one place.
Check out the [ActiveCampaign integration guide](/integrations/activecampaign) for more details.
## Improved Algolia integration for more control over indexing
We’ve overhauled the Algolia integration to give you total control over how Algolia indexes your content.
Instead of relying on hidden internal rules, you can now use GraphQL queries to explicitly define exactly which model fields sync to Algolia and precisely when to generate separate records.

Now you have full control over the data flow, ensuring search results are more relevant and streamlining your Algolia index size by syncing only what you actually need.
Check out the [Algolia setup guide](/integrations/algolia) for more details.
## Webhook events updated with locale info
We've updated two webhook events with locale info to give you more accurate automation and clearer visibility into content item changes and deletions.
1. Previously, the `content_item.deleted` event only triggered for an entire content item deletion.
Now, this event also fires when a single language variant of a content item is deleted.
You can differentiate between a full deletion and a partial (locale-specific) deletion using the following parameters:
- `scope`: Values can be either `item` (full deletion) or `locale` (partial deletion).
- `locale`: If the `scope` value is `locale`, this value is the locale string, for example: `en-US`.
2. Previously, the `content-item.changed` event did not indicate which language variant of a content item was actually changed.
Now, this event fires for every language variant updated.
The `locale` parameter value indicates exactly which is updated, for example: `en-US`.
Check out the [webhooks guide](/development/best-practices/webhooks) for more details.
## Choosing environments to search shared content items
Previously, when you added searchable models to a *Stack* or *Content reference* field in a shared model, you could either choose the model to be available to all environments or a single environment.
Now, you can choose specific environments for searchable models.
For example, when editors need to search content items common to multiple sites, but shouldn't view or include test content items.

This means a cleaner editor experience because they see exactly what they need.
Check out the [Shared schema guide](/project-setup/architecture-scenarios/shared-schema#define-environments-for-allowed-models) for more details.
## Environment-scoped preview for shared model
Previously, when you set up preview URLs in a shared model, editors in any environment in the organization could see the full list of preview URLs you added.
Now, you can scope preview URLs to individual environments when setting them up.

This cleans up the UI for visual editing as editors only see preview options relevant to their environment, saving them time and avoiding unnecessary scrolling and clicking wrong preview options.
For more details, check out the [preview setup guide](/project-setup/setting-up-previews-and-visual-editing#set-up-preview-urls).
## AI-generated text for images
Based on your feedback on the [Beta release](#ai-generated-alt-text-for-images-beta-release), we’ve officially launched AI-generated text for images.
When uploading new images get AI-generated alt text, descriptions, or comma-separated keywords instantly.

This means you save time on manual entry and boost SEO rankings without the extra effort.
Check out the [AI integration guide](/integrations/image-processing/ai) to activate this feature.
## Uploading files without an official MIME type
Previously, when you uploaded a file without an official MIME type, this file could not be processed.
For example, files with a `.bgi` extension.
We've updated the file upload to allow files without an official MIME type, using the file extension to set a fallback [MIME type](https://mimetype.io/all-types).
Also, when a MIME type has multiple extensions, we keep the original extension of the file.
This means your files remain in their original format, ready to use in content items and processed.
For more details, check out the [uploading assets](/content-management/managing-assets/managing-assets#uploading-assets) doc for more details.
## Introducing the Propeller commerce integration
The new *Propeller* integration allows your team to easily include Propeller products in content items.

This means content editors can work more efficiently and error-free by simply choosing the relevant Propeller entries directly in Prepr.
For more details, check out the [Propeller integration guide](/integrations/propeller).
## Defining percentage-based field widths
With a new option to set field width, we've replaced the field columns fixed structure with a more flexible percentage-based option for model and component fields.

This visual improvement means editors can more easily and quickly scan and edit text fields in content items.
Check out the [models guide](/content-modeling/managing-models#defining-field-width) for more details.
## Improved schema sync visibility
We've updated the schema UI and since syncing is an important schema action, we've moved the sync schema action to the top of the page.

With this update, you also have clarity on which repo, branch and commit the schema was last synced with and whether the schema is in sync with the branch or vice versa.
Check out the [schema sync guide](/development/working-with-cicd/syncing-a-schema#check-the-result) for more details.
## New field setting to declutter content item filter menus
With the new **Show in filters** setting, developers can declutter the editing experience by hiding *List*, *Stack* and *Content reference* fields from the content item filter menus.

It’s about reducing noise and helping your editors focus on the data that actually matters.
For example, when editors search *Product* content items, the product rating (list field) doesn't add value to the search because it would return too many results.
Check out the field settings of the [stack field](/content-modeling/field-types#stack-field), [list field](/content-modeling/field-types#list-field) or [content reference field](/content-modeling/field-types#content-reference-field) for more details.
## Searching through your schema instantly
Find your models, components, enumerations, and remote sources instantly with the new schema search bar.

This way you stay focused on building your schema rather than searching.
Check out the [models guide](/content-modeling/managing-models#search-models) for more details.
## Improved commenting on content items
You can now add content item comments to specific fields and for a specific locale of the content item, making it possible to provide more granular and actionable feedback.

This means you can follow your editing review process directly in Prepr speeding up your team collaboration and publication of content items.
Check out the [collaboration guide](/content-management/collaboration#commenting) for more details.
## Manage content with AI agents using the new Prepr MCP server
With the Prepr MCP server, you can create AI agents to manage content in your Prepr CMS environment with natural language prompts.
For example, editors who usually write article posts in a tool like Notion can ask the AI agent to create content items for these posts.

This means you can automate the grunt work so your team can focus on more strategic tasks.
Check out the [Prepr MCP server overview](/prepr-mcp-server) to learn how to set it up for AI agents.
## Managing custom event types
You can now set the **Display name** and the **Visibility** of custom event types directly in Prepr.
For example, to change a technical event type name `Scrolled50` to something like *Scrolled 50% of the page* or to hide old events with an outdated event type.

This means that you and your marketers get a clear clutter-free view of events and only see relevant metrics.
Check out the [Event type settings](/data-collection/recording-events#managing-custom-event-types) for more details.
## Expanded trigger criteria for *content\_item.invalidated* webhook event
We’ve updated the criteria for the `content_item.invalidated` webhook event to provide better synchronization between your external systems and published content.
Previously, this event was triggered for external system changes to a remote source item.
Now, it also triggers for [API `PATCH`](/mutation-api/content-items-create-update-and-destroy#patch-a-content-item) updates. This means if you update a published content item via the API `PATCH` request, the `content_item.invalidated` event will fire automatically.
Check out the [webhooks guide](/development/best-practices/webhooks#content-item-events) for more details.
## Fixed validation for required fields after hidden conditional sections
We fixed an issue where required fields following a conditional section were incorrectly ignored when that section was not shown.
Previously, this could allow content to be published without completing required fields.
With this fix, required fields after a hidden conditional section are now validated correctly before publication.
## Schema validation based on new Prepr schema spec
We've just released a [Prepr schema validation](https://github.com/preprio/action-schema-validation) using the new [Prepr schema spec](https://github.com/preprio/action-schema-validation/blob/main/spec/2026-03-05.json5).
Now, you can automatically validate your schema JSON files in *GitHub* to ensure you import a valid Prepr schema every time.
This release lays the foundation for the next steps to use AI to generate a fully validated Prepr schema.
For more details, check out the [schema validation guide](/development/working-with-cicd/validating-a-schema).
## Prepr Next.js package version 2.2.5 is available
We've just released a new version of the Prepr Next.js package. Version 2.2.5 includes a manual override of the `fast-xml-parser` to solve the `CVE-2026-25128` DoS vulnerability.
We strongly recommend you upgrade your installation of the Prepr Next.js package, where applicable.
For more details, check out the [Prepr Next.js package guide](/prepr-nextjs-package).
## Remote source settings preserved in schema sync
We’ve updated the behavior of the *Direct schema sync* and the schema import (via *GitHub*, *GitLab*, *Bitbucket* or *Azure DevOps*) for existing remote sources.
Previously, an existing remote source was always fully synced.
From now on, the URL, headers, and image domains of an existing remote source will no longer be updated during a schema sync and will be ignored instead.
This matches the current behavior of *Preview URLs* and helps prevent manual rework after each sync, especially when test or development URLs need to remain unchanged.
Check out the [Schema sync guide](/development/working-with-cicd/syncing-a-schema) for more details.
## New Prepr Next.js package version 2.2.x is available
The newest version of the Prepr Next.js package now automatically detects and handles stega encoding. Stega encoding uses hidden characters so editors can click and edit content directly from the preview.
This update means you no longer need to write extra code to clean up these hidden characters which could cause misalignment of some UI elements in the live preview.
Check out the [Prepr Next.js package guide](/prepr-nextjs-package) for more details.
## Legacy API deprecation on August 1st, 2026
As part of our effort to streamline our API infrastructure, the legacy Prepr API endpoint `api.eu1.prepr.io` will expire and be removed on August 1st, 2026.
If your application currently uses `api.eu1.prepr.io`, update your implementation before then.
For write operations (mutations), update your endpoint to `mutation.prepr.io`.
For read-only operations (queries), we strongly recommend updating your endpoint to `cdn.prepr.io`.
Contact [Prepr support](mailto:support@prepr.io) if you have any specific questions.
## AI-generated alt text for images (Beta release)
When uploading new images you can now get AI-generated text values instantly. For example: alt text, specific descriptions, or comma-separated keywords.
This means you save time on manual entry and boost SEO rankings without the extra effort.

Contact [Prepr support](mailto:support@prepr.io?subject=Request%20AI%20activation%20for%20image%20text%20values) to activate AI-generated text for images in your environment.
## Improved linked content visibility
With the improved linked content visibility, you can now get a high-level view of linked content items from the content item list.

This removes the need to open individual items to investigate dependencies and therefore speeds up your review of linked content items.
For more details, check out the [content management doc](/content-management/managing-content/managing-content-items#view-linked-content-items).
## SSO-ready invites speed up sign-ins
Inviting new users to Prepr is now smoother when you use SSO, for example, *Microsoft Entra ID* or *SAML 2.0*.
As an administrator, you can now assign roles upfront by simply inviting the new SSO user.
This means users no longer need to wait for an admin to grant access after their first SSO sign-in.
When the user clicks the **Activate** link in the invite email, Prepr automatically routes them to the correct SSO sign-in flow (instead of email + password), so they can start working right away.
Check out the [managing users guide](/project-setup/managing-users#send-invitation-to-sign-in) for more details.
## New webhook event for remote source item changes
We’ve added a new event to our webhooks: `content_item.invalidated`.
This new event triggers whenever remote source data linked to a content item changes.
This means you can now set up a webhook to notify your front end the moment the remote source data becomes stale.
For more details, check out the [webhooks guide](/development/best-practices/webhooks#content-item-events).
## Additional granular permissions to manage content items
In addition to the [action-based content permissions](/stay-updated/changelog2025#introducing-granular-permissions-to-manage-content-items) we released recently, you can now extend user role permissions and content item access with the following additional features:
- *Direct content item sharing* - You can now grant specific users access to individual content items. This allows you to easily work with freelancers or agencies while maintaining strict data privacy.

- *On-demand access requests* - When users try to open a linked content item they don't have access to, they can directly request access within Prepr with this new workflow.

- *Show my items only* - You have a new setting to allow a user to only access content items they've created or items explicitly shared with them. For example, a freelance editor who only needs to access content items they're assigned to work on.

- *Scoped content access* - Allows users to access only content items in specific workflow stages. For example: A translator who only needs to access content items in the *Review* or *Translation* workflow stages.

These new access right features enhance data security, prevent accidental errors and increase user focus for certain roles, like freelance editors.
Check out the [user role guide](/project-setup/managing-roles-and-permissions#content-management-permissions) for more details.
## Introducing the ProspectPro integration
With the new ProspectPro integration, you can easily connect Prepr to this B2B prospecting platform.
This integration lets you segment website visitors based on their company industry and company size.

By segmenting visitors this way you can personalize content for an enhanced user experience for your B2B audience.
Check out the [ProspectPro integration guide](/integrations/prospectpro) for more details.
## Activate integration with Prepr directly in HubSpot Marketplace
We've introduced a streamlined authorization flow that allows you to activate the integration with Prepr directly from the HubSpot Marketplace.
This makes the process seamless and fully contained within the HubSpot environment.

Check out the [HubSpot integration guide](/integrations/hubspot) for more details.
Source: https://docs.prepr.io/changelog
---
# Prepr's product roadmap
We’re constantly improving our products, integrations, and services. Learn about features we're working on and upcoming improvements.
## Q3 & Q4 2026
### Legacy API endpoint sunset, August 1st
The legacy [api.eu1.prepr.io](http://api.eu1.prepr.io/) endpoint will be permanently retired on **August 1, 2026**. Customers still using this endpoint should migrate to the new infrastructure to ensure uninterrupted service.
Write operations should move to [mutation.prepr.io](http://mutation.prepr.io/), while read-only GraphQL queries are recommended to use the high-performance [cdn.prepr.io](http://cdn.prepr.io/) endpoint. This migration provides improved scalability, performance, and a simplified API architecture going forward.
### AI prompts to generate context for segments and environments
Creating effective AI context for audience segments and environments often requires detailed descriptions that can be difficult to write from scratch. We’re making this easier by providing guidance that users can use in their preferred AI tools to generate high-quality audience descriptions.
These richer descriptions help AI better understand target audiences, including their motivations, interests, behavior, tone of voice, and communication style, resulting in more relevant and personalized content generation throughout Prepr.
### Schema management with MCP server
We’re expanding our MCP (Model Context Protocol) server to support schema management, enabling AI assistants and external tools to safely inspect and manage Prepr content models. Instead of limiting MCP to content operations, developers will be able to retrieve schema information, understand model relationships, and automate schema-related workflows through a standardized interface.
This lays the foundation for more advanced AI-assisted development experiences, such as generating integrations, validating content models, synchronizing schemas between environments, and supporting developer tooling that understands the complete structure of a Prepr project.
### Enhanced MCP server features and skills
The MCP server will gain a broader set of capabilities, allowing AI assistants to perform more complex tasks while maintaining secure access control.
Alongside expanded functionality, we’re introducing reusable “skills” that package common workflows into higher-level actions (for example, the described use cases), making AI integrations more powerful and easier to use.
### Enhanced content item filtering
Editors will be able to filter content based on whether specific fields are empty or populated, making it much easier to identify incomplete content across large repositories.
This initial enhancement focuses on empty and not empty conditions for fields such as SEO metadata, alt text, descriptions, and other editorial content.
It also establishes the foundation for more advanced filtering capabilities in future releases, including comparison operators and more sophisticated search criteria.
### Multi-select enumerations
Enumeration fields will now support selecting multiple values instead of being limited to a single option.
This allows editors to classify content more flexibly without creating unnecessary additional fields or complex reference structures.
Multi-select enumerations are ideal for scenarios such as assigning multiple categories, content types, product features, or labels while preserving the simplicity and performance of enumeration fields.
### Localization per field
Prepr currently localizes every field in a content model, requiring editors to manage values separately for every language.
We’re introducing field-level localization, allowing each field to be configured as either localized or shared across all locales. When a content item is first created, that locale automatically becomes the source locale for all shared fields.
Localized fields continue to maintain independent values per language, making them ideal for titles, body content, and SEO fields.
Non-localized fields automatically inherit their value from the source locale, ensuring consistency for assets, references, shared CTAs, authors, and other content that should remain identical across every translation.
This significantly reduces duplicate editing work while keeping multilingual content synchronized.
### Multiple shared schemas per organization
Organizations will be able to maintain multiple shared schemas within a single organization, enabling proper support for Development, Test, Acceptance, and Production environments without requiring multiple organizations.
Each shared schema functions as an independent schema group with predictable behavior and governance.
This simplifies user management, billing, permissions, and operational workflows while providing clear separation between DTAP environments.
### Improved experiment overview and setup guidance
We’re redesigning the experimentation experience to make creating and managing experiments much more intuitive.
During setup, editors will receive clearer guidance about required tracking, success metrics, traffic allocation, and experiment configuration, reducing mistakes before an experiment goes live.
The experiment overview will also become more informative, giving teams better visibility into experiment status, health, configuration quality, and expected next steps.
### AI-first content management
We’re exploring an AI-first experience where the Content Items page becomes a conversational workspace instead of a traditional content list. Users can simply describe what they’re looking for or what they want to accomplish, while AI helps locate content, answer questions, and guide them through editorial workflows.
### Page view metrics in content workflows
Content performance should be visible where editors work. Based on the Metrics configuration defined on each content model, the content item list and detail pages will display the relevant traffic, engagement, and performance metrics directly alongside editorial information. By surfacing the metrics, editors can quickly understand how content performs without navigating to separate analytics dashboards. This creates a more integrated editorial workflow where content quality and performance are evaluated together.
### AI-powered GEO and AEO content checks
We’re expanding Content Checks with AI-powered guidance focused on Generative Engine Optimization (GEO) and Answer Engine Optimization (AEO). Rather than only validating traditional SEO best practices, Prepr will help editors create content that’s better suited for AI search, knowledge retrieval, and modern discovery platforms.
The AI assistant provides recommendations around content structure, completeness, clarity, and semantic quality, helping teams produce content that’s easier to index, understand, and surface across both search engines and AI-powered experiences.
### Automatic redirects for slug changes
Changing a page slug should never result in broken links. Prepr will automatically generate redirects whenever an editor manually changes the slug of a published content item. This eliminates the need to manually configure redirects after URL changes, preserving existing links, maintaining search engine rankings, and ensuring visitors are seamlessly redirected to the updated page.
### Redesigned access token management
We’re redesigning the access token experience to make token creation significantly easier to understand. Instead of presenting every configuration option on a single page, users will first choose the type of token they want to create, such as GraphQL, REST API, Personal Access Token, or MCP, and are then guided through only the settings relevant to that token.
By separating token types into dedicated flows, the interface reduces cognitive load, improves permission management, and provides a scalable foundation for introducing future authentication methods without increasing complexity.
### Ongoing editorial improvements
We’re continuing to refine the day-to-day editorial experience with a series of productivity enhancements across the CMS. These include word counters for text fields, manual AI regeneration of image alt text, favoriting frequently used models, improved asset upload workflows, simplified Stack editing, and various usability improvements throughout the editor.
Additional enhancements include click-to-edit capabilities and commenting support within the Next.js package, more flexible asset validation, and streamlined content creation workflows. While individually small, these improvements collectively reduce friction and help editors work faster and more efficiently.
### Improved CRM/CDP integrations for audience segmentation
We’re strengthening CRM and Customer Data Platform integrations to make external audience segmentation a first-class part of adaptive content delivery.
Editors will be able to use audience segments that already exist in connected CRM and CDP platforms, eliminating the need to recreate segmentation rules inside Prepr.
By combining external customer intelligence with Prepr’s personalization capabilities, organizations can deliver more relevant content experiences based on campaign audiences, customer lifecycle stages, lead status, or other existing segmentation strategies. This creates a more seamless personalization workflow while maximizing the value of existing marketing technology investments.
Source: https://docs.prepr.io/roadmap
---
# Setting up your production-ready project
*Welcome to Prepr, a data-driven headless CMS with a built-in personalization engine and optimization features. Learn about the different parts that you need to set up your project with Prepr and the steps to follow to make the best use out of its features for your front-end applications.*
If you can't wait to dive straight into Prepr, follow the [Quick start guide](/quick-start-guide) to get your feet wet.
Learn more about the structure of your Prepr project and then move on to the step-by-step guide to set it up.
Looking for something specific? Check out the detailed resources below.
Source: https://docs.prepr.io/project-setup
---
# Content modeling
*Explore the resources below to get started with content modeling and learn how to set up a well-defined schema in Prepr CMS.*
Before diving into Prepr, learn the basics about content modeling and how to model content using some typical examples like a *Blog*, *Page* and *Personalization*.
Dive into Prepr and learn how to set up a schema by managing models, components, setting up remote sources and other more advanced features.
Source: https://docs.prepr.io/content-modeling
---
# Connecting a front-end framework
The flexibility of a data-driven headless CMS allows you to connect your favorite front-end framework, deliver content and optimize and personalize the visitor journey.
Source: https://docs.prepr.io/connecting-a-front-end-framework
---
# Developing with Prepr CMS
*Discover everything you need to know to develop with Prepr CMS, including resources for connecting front-end frameworks, best practices, managing CI/CD pipelines and integration guides.*
Source: https://docs.prepr.io/development
---
# Content management
Discover all you need to know about managing content items, how to handle assets, localizing content, and collaboration when working with content items in Prepr CMS.
Source: https://docs.prepr.io/content-management
---
# Data collection
*Prepr CMS offers several data-driven features such as Adaptive content, A/B testing, and Recommendations.
To power these features, Prepr requires visitor data.
This data is essential for creating segments for personalization, evaluating A/B test results, and determining relevant recommendations.
Discover all you need to know about collecting and managing visitor data to make the most out of these features.*
Learn more about data collection key concepts and move on to the step-by-step guide to set it up.
Looking for something specific? Check out the detailed resources below.
Source: https://docs.prepr.io/data-collection
---
# Personalization
*Discover all you need to know about making your website adaptive by setting up personalization, managing segments and creating adaptive content to improve engagement and user experience.*
Source: https://docs.prepr.io/personalization
---
# A/B testing
*Discover all you need to know about setting up and using Prepr CMS A/B testing to improve engagement and user experience.*
Source: https://docs.prepr.io/ab-testing
---
# Setting up recommendations
Estimated duration: 15-30 minutes
## Introduction
Prepr allows you to add recommendations to your web application quickly. This tutorial demonstrates how to deliver recommendations using the Prepr GraphQL API. Follow the step-by-step guide below.
## Use case
Deliver highly relevant content recommendations to the visitors of your websites and apps using the GraphQL API to increase engagement. Let's examine an everyday use case: an article that you want to show recommendations for.
Recommendations are usually displayed below an article to entice visitors to view more content. With Prepr, you can display three types of recommendations:
- Similar items
- People also viewed items
- Popular items
### Similar items
Similar items are recommendations similar to the article the visitor is currently viewing. Because it's on the same topic, or because it's by the same author, or maybe it's approximately the same length.
With this recommendation type, the algorithm looks at the characteristics of the article (content, meta-data, links to other articles, etc.) and determines what the most relevant related articles are based on a score.
### People also viewed items
People also viewed items are items that other visitors also viewed along with the item that a visitor is currently viewing. The algorithm looks at the current item and determines the visitors that viewed this item. It then lists the other items that these visitors also viewed.
### Popular items
Popular items, the name says it all, are the most viewed items. The algorithm looks at how often an item has been viewed for this recommendation type. You could say this is not a recommendation but sorting by popularity.
### Content structure
Before you start generating the recommendations, it’s a good idea to look at the content model of the article. That structure largely determines how accurate the recommendations become.

In this case you have an article with a number of content fields: title, intro and content. In addition, there are references to an author and to one or more categories. And finally, an editor can manually add tags. Prepr uses all this information to provide the most relevant recommendations.
## Creating a Prepr CMS account
Following this guide requires a (free) Prepr CMS account.
- Go to https://signup.prepr.io/ and sign up
- Follow the [quick start guide](/quick-start-guide) to add models and content items
## Querying the API for similar items
To retrieve similar items, you must first have the ID of the content item where you want to show recommendations for
### Getting a content item ID
- Go to the **Content** tab and open the content item for which you want to retrieve similar items.
- Click the icon and choose the **Copy item ID** option.

### Retrieving similar items
- Click the icon and choose the **Access tokens** option.
- Click to open the *GraphQL Production* token details.
- Click the **Open in API explorer** link.

View the API reference for all [API authentication details](/graphql-api/authorization)
- Add the following query to the explorer:
```graphql copy
query {
Similar_Articles(
id: "0cbe2455-124c-4820-b2ac-dcc4261e150c"
) {
items {
_id
title
}
}
}
```
- Replace the **id** value with the ID of your content item
Note the **Similar\_Articles** query type at the beginning of the query. For each content model in your environment the Prepr creates a corresponding GraphQL type with a similarity algorithm. The plural type name of the content model is prefixed with Similar\_. For example Article generates a type Similar\_Articles. You can find all these options in the API Explorer.
- Run the query
The result should look something like:
```json copy
{
"data": {
"Similar_Articles": {
"items": [
{
"_id": "885b71ac-5a4b-4d2e-9770-a4a6f20425e9",
"title": "15 Tips On How To Brand Yourself Online"
},
{
"_id": "f1d9b142-883f-4964-8d8f-cd2f255330a2",
"title": "Why Customization Is Key For Entrepreneurs In The Digital Age"
},
{
"_id": "dd42b8c4-4773-4fdd-a71f-87e57b35beaa",
"title": "Building User Trust In UX Design"
},
{
"_id": "79c453ed-ffe2-4aec-8fb2-f549e3775264",
"title": "Building A Video Streaming App With Nuxt.js"
},
{
"_id": "5a53b271-898e-4eef-b877-5863b34b6ff9",
"title": "UI Design Testing Tools I Use All The Time"
},
{
"_id": "ca0e0d37-600b-42f7-a013-9b0f8e18f127",
"title": "The Evolution Of Jamstack"
},
{
"_id": "dd3309eb-df84-4e4b-8fdf-80b31b8eb58a",
"title": "The Rise Of Design Thinking As A Problem Solving Strategy"
},
{
"_id": "f4b65d11-c2e2-4320-a363-e7d420d03ed2",
"title": "Modeling A GraphQL API For Your Blog"
}
]
}
}
}
```
That's all. You now have recommendations that you can show in your application. Let's see how we can improve the recommendations even more.
### Filtering the result
The previous example included all items for determining recommendations. But often, you want to make the result even more accurate. For example, you may want to query only recommended articles published recently or in a particular category.
So let's see how you can do that.
- Expand the query with the following parameters:
```graphql copy
query {
Similar_Articles(
id: "0cbe2455-124c-4820-b2ac-dcc4261e150c"
where: {
_publish_on_gt: "2021-01-01T00:00:00+00:00"
categories: {
_slug_any: [
"ux-design", "development"
]
}
},
limit: 3
) {
items {
_id
title
}
}
}
```
- We added **\_publish\_on\_gt**: "2021-01-01T00:00:00+00:00" to show only articles published after January 1, 2021. Note the underscore at the beginning of the parameter.
- We added categories: **`{ _slug_any: [ "ux-design", "development" ] }`** to limit the result to only articles in the “UX Design” or the “Development” categories. Note the square brackets because we’re dealing with an array.
- We added **limit: 3** to limit the number of results to three items
The result should look something like:
```json copy
{
"data": {
"Similar_Articles": {
"items": [
{
"_id": "dd42b8c4-4773-4fdd-a71f-87e57b35beaa",
"title": "Building User Trust In UX Design"
},
{
"_id": "5a53b271-898e-4eef-b877-5863b34b6ff9",
"title": "UI Design Testing Tools I Use All The Time"
},
{
"_id": "dd3309eb-df84-4e4b-8fdf-80b31b8eb58a",
"title": "The Rise Of Design Thinking As A Problem Solving Strategy"
}
]
}
}
}
```
View the API reference for [all filter options](/graphql-api/fetching-filtering-collections).
### Optimizing the recommendation algorithm
The recommendation algorithm for similar items finds items based on the following criteria:
|Criteria|Default weight|Description|
|--------|----|------|
|Topics |1.4| Highest weight. Topics are extracted automatically by the AI text analysis engine. It identifies specific people, places, or concepts within the item text and finds items with matching topics.|
|Content References|1.2| Matches content items linked to the main item through any content references whether in the *Stack*, *Component* or *Dynamic content* field. For example, articles in the same category and articles by the same author.|
|Tags|0.8| Matches items based on tag values in the main content item.|
You can tweak the default logic to align with your own business priorities.
For example, if your content items are related more by their common tags rather than linked items, you can increase the importance of *Tags*.
Here’s an example of how that works:
```graphql copy
query {
Similar_Articles(
id: "0cbe2455-124c-4820-b2ac-dcc4261e150c"
where: {
_publish_on_gt: "2021-01-01T00:00:00+00:00"
categories: {
_slug_any: [
"ux-design", "development"
]
}
},
limit: 3
rules: {
entities: 0
tags: 1
references: 0.5
}
) {
items {
_id
title
}
}
}
```
- We added rules: **`{ entities: 0, tags: 1, references: 0.5 }`** to indicate that topics should not be included and that the tag criteria has higher priority than references.
We recommend starting with the default setting and adding rules only if the result does not meet your expectations.
View the [API reference](/graphql-api/personalization-recommedations-similar-content) for all rules options.
## Querying the API for People Also Viewed items
To retrieve *People also viewed* items, you need the ID of the content item that you want to show recommendations for.
### Getting a content item ID
- Go to the **Content** tab and open the content item you want to use as the reference.
- Click the icon and choose the **Copy item ID** option.

Now that you have the *Content Item ID*, you can track visitors who viewed the same content item. That information can be used to display the *People Also Viewed* items.
### Capturing views
You can capture view events in Prepr using a lightweight piece of JavaScript, the *Prepr Tracking Code*.
Follow the steps in the [Tracking setup guide](/data-collection/setting-up-the-tracking-code#enabling-prepr-tracking) to add the *Prepr Tracking Code*.
Once you've enabled tracking, you can then [add a meta tag](/data-collection/recording-events#tracking-content-items) to record view events on content items.
Check out the [Events doc](/data-collection/recording-events) for all tracking options.
### Retrieving People also viewed items
Now that you’re tracking the visitors that view an item, you can retrieve the other items that these visitors also viewed.
- Click the icon and choose the **Access tokens** option to view all the access token.
- Click to open the *GraphQL Production* token details.
- Click the **Open in API explorer** link.

View the API reference for all [API authentication details](/graphql-api/authorization)
- Add the following query to the explorer:
```graphql copy
query {
PeopleAlsoViewed_Articles (
id : "90276002-d628-4ba6-b3c8-f756c486b67b" )
{
items {
_id,
title
}
}
}
```
Note the **PeopleAlsoViewed\_Articles** query type at the beginning of the query. Prepr automatically provides you with a `PeopleAlsoViewed` query for each model. For example, if your model name is Article, you also get the PeopleAlsoViewed\_Articles query. You can find all these query options in the API Explorer.
The result should look something like this:
```json copy
{
"data": {
"PeopleAlsoViewed_Articles": {
"items": [
{
"_id": "0cbe2455-124c-4820-b2ac-dcc4261e150c",
"title": "How to set up a Google Ads account"
},
{
"_id": "dd42b8c4-4773-4fdd-a71f-87e57b35beaa",
"title": "Building User Trust In UX Design"
},
{
"_id": "79c453ed-ffe2-4aec-8fb2-f549e3775264",
"title": "Building A Video Streaming App With Nuxt.js"
},
{
"_id": "5a53b271-898e-4eef-b877-5863b34b6ff9",
"title": "UI Design Testing Tools I Use All The Time"
},
{
"_id": "dd3309eb-df84-4e4b-8fdf-80b31b8eb58a",
"title": "The Rise Of Design Thinking As A Problem Solving Strategy"
},
{
"_id": "f1d9b142-883f-4964-8d8f-cd2f255330a2",
"title": "Why Customization Is Key For Entrepreneurs In The Digital Age"
},
{
"_id": "885b71ac-5a4b-4d2e-9770-a4a6f20425e9",
"title": "15 Tips On How To Brand Yourself Online"
},
{
"_id": "ca0e0d37-600b-42f7-a013-9b0f8e18f127",
"title": "The Evolution Of Jamstack"
},
{
"_id": "f4b65d11-c2e2-4320-a363-e7d420d03ed2",
"title": "Modeling A GraphQL API For Your Blog"
}
]
}
}
}
```
### Filtering the result
The previous example includes all items that other users viewed. But often, you want to make the result even more accurate. For example, only show items published recently or in a particular category.
So let's see how you can do that.
- Expand the query with the following parameters:
```graphql copy
query {
PeopleAlsoViewed_Articles(
id : "90276002-d628-4ba6-b3c8-f756c486b67b",
where: {
_publish_on_gt: "2021-01-01T00:00:00+00:00"
categories: {
_slug_any: [
"ux-design", "development"
]
}
},
limit: 3 ){
items {
_id
title
}
}
}
```
- We added `_publish_on_gt: "2021-01-01T00:00:00+00:00"` to show only articles published after January 1, 2021. Note the underscore at the beginning of the parameter.
- We added categories: `{ _slug_any: [ "ux-design", "development" ] }` to limit the result to only articles in the “UX Design” or the “Development” categories. Note the square brackets because you’re dealing with an array.
- We added `limit: 3` to limit the number of results to three items.
Result:
```json copy
{
"data": {
"PeopleAlsoViewed_Articles": {
"items": [
{
"_id": "dd42b8c4-4773-4fdd-a71f-87e57b35beaa",
"title": "Building User Trust In UX Design"
},
{
"_id": "79c453ed-ffe2-4aec-8fb2-f549e3775264",
"title": "Building A Video Streaming App With Nuxt.js"
},
{
"_id": "5a53b271-898e-4eef-b877-5863b34b6ff9",
"title": "UI Design Testing Tools I Use All The Time"
}
]
}
}
}
```
## Querying the API for most popular items
To show the most popular items, you must track how often visitors view content items. That information can be used to display the most popular items.
### Capturing views
You can capture view events in Prepr using a lightweight piece of JavaScript, the *Prepr Tracking Code*.
Follow the steps in the [Tracking setup guide](/data-collection/setting-up-the-tracking-code#enabling-prepr-tracking) to add the *Prepr Tracking Code*.
Once you've enabled tracking, you can then [add a meta tag](/data-collection/recording-events#tracking-content-items) to record view events on content items.
Check out the [Events doc](/data-collection/recording-events) for all tracking options.
### Retrieving most popular items
Now that you’re tracking how often visitors view an item, you can retrieve the most popular items.
- Click the icon and choose the **Access tokens** option.
- Click to open the *GraphQL Production* token details.
- Click the **Open in API Explorer** link.

View the API reference for all [API authentication details](/graphql-api/authorization)
- Add the following query to the explorer:
```graphql copy
query {
Popular_Articles {
items {
_id
title
_views
}
}
}
```
Note the **Popular\_Articles** query type at the beginning of the query. Prepr automatically provides you with a popularity query for each model. If your model name is Article, you also get the Popular\_Articles query. You can find all these query options in the API Explorer.
Note the **\_views** system field that shows the number of views for a content item. This field is optional. We recommend not using it to optimize cache efficiency.
The result should look something like this:
```json copy
{
"data": {
"Popular_Articles": {
"items": [
{
"_id": "0cbe2455-124c-4820-b2ac-dcc4261e150c",
"title": "How to set up a Google Ads account",
"_views": 83
},
{
"_id": "dd42b8c4-4773-4fdd-a71f-87e57b35beaa",
"title": "Building User Trust In UX Design",
"_views": 59
},
{
"_id": "79c453ed-ffe2-4aec-8fb2-f549e3775264",
"title": "Building A Video Streaming App With Nuxt.js",
"_views": 49
},
{
"_id": "5a53b271-898e-4eef-b877-5863b34b6ff9",
"title": "UI Design Testing Tools I Use All The Time",
"_views": 40
},
{
"_id": "dd3309eb-df84-4e4b-8fdf-80b31b8eb58a",
"title": "The Rise Of Design Thinking As A Problem Solving Strategy",
"_views": 29
},
{
"_id": "f1d9b142-883f-4964-8d8f-cd2f255330a2",
"title": "Why Customization Is Key For Entrepreneurs In The Digital Age",
"_views": 21
},
{
"_id": "885b71ac-5a4b-4d2e-9770-a4a6f20425e9",
"title": "15 Tips On How To Brand Yourself Online",
"_views": 11
},
{
"_id": "ca0e0d37-600b-42f7-a013-9b0f8e18f127",
"title": "The Evolution Of Jamstack",
"_views": 9
},
{
"_id": "f4b65d11-c2e2-4320-a363-e7d420d03ed2",
"title": "Modeling A GraphQL API For Your Blog",
"_views": 5
}
]
}
}
}
```
### Filtering the result
The previous example included all items for retrieving the most popular items. But often, you want to make the result even more accurate. For example, only show items published recently or in a particular category.
So let's see how you can do that.
- Expand the query with the following parameters:
```graphql copy
query {
Popular_Articles(
where: {
_publish_on_gt: "2021-01-01T00:00:00+00:00"
categories: {
_slug_any: [
"ux-design", "development"
]
}
},
limit: 3
) {
items {
_id
title
_views
}
}
}
```
- We added `_publish_on_gt: "2021-01-01T00:00:00+00:00"` to show only articles published after January 1, 2021. Note the underscore at the beginning of the parameter.
- We added categories: **`{ _slug_any: [ "ux-design", "development" ] }`** to limit the result to only articles in the “UX Design” or the “Development” categories. Note the square brackets because you’re dealing with an array.
- We added `limit: 3` to limit the number of results to three items.
Result:
```json copy
{
"data": {
"Popular_Articles": {
"items": [
{
"_id": "dd42b8c4-4773-4fdd-a71f-87e57b35beaa",
"title": "Building User Trust In UX Design",
"_views": 59
},
{
"_id": "79c453ed-ffe2-4aec-8fb2-f549e3775264",
"title": "Building A Video Streaming App With Nuxt.js",
"_views": 49
},
{
"_id": "5a53b271-898e-4eef-b877-5863b34b6ff9",
"title": "UI Design Testing Tools I Use All The Time",
"_views": 40
}
]
}
}
}
```
## Want to learn more?
Check out the following chapters:
- [A/B testing](/ab-testing/setting-up-ab-testing)
- [Personalization](/personalization/setting-up-personalization)
## Schedule a free consultation
Do you want to get started with recommendations but still have questions or want a demo?
[Schedule a free call](https://prepr.io/get-a-demo)
with a Prepr solution engineer.
Source: https://docs.prepr.io/recommendations
---
# Integrations
Extend Prepr CMS with one of the standard integrations listed below. If you need to build a custom integration, check out the [creating a custom remote source](/content-modeling/creating-a-custom-remote-source) or [using webhooks](/development/best-practices/webhooks) resources instead.
Source: https://docs.prepr.io/integrations
---
# AI-friendly documentation
*From this guide, you'll learn how to use Prepr's machine-readable documentation formats with AI coding assistants for faster, more accurate development.*
## Introduction
Prepr's documentation is available in LLM-friendly formats that make it easy to integrate with AI coding assistants like Claude, ChatGPT, Cursor, and GitHub Copilot. These formats follow the [llms.txt](https://llmstxt.org/) standard, providing machine-readable documentation that AI assistants can use as context when helping you build with Prepr.
By adding Prepr documentation to your AI assistant, you'll get:
- Accurate answers based on the latest Prepr documentation
- Code suggestions that follow Prepr best practices
- Faster development with context-aware assistance
- Reduced errors from outdated or incorrect information
## Available formats
Prepr documentation is available in three LLM-friendly formats:
### llms.txt
The `llms.txt` file provides an index of all documentation pages with titles and descriptions.
**URL:**
**Best for:** Quick reference and helping AI assistants find relevant documentation sections.
### llms-full.txt
The `llms-full.txt` file contains the complete Prepr documentation in a single, machine-readable text file.
**URL:**
**Best for:** Providing full context to AI assistants for comprehensive answers about Prepr.
### Markdown format (.md)
Any documentation page can be accessed in markdown format by appending `.md` to the URL.
**Example:**
**Best for:** Getting the raw markdown content of individual pages for focused context.
## Using with AI
## Tips for using AI assistants with Prepr
Source: https://docs.prepr.io/ai-friendly-docs
---
# AI agent-ready best practices for front-end applications
*This guide gives you some tips on how to build a front-end application that AI crawlers, RAG (Retrieval-Augmented Generation) agents, and LLM search engines can easily fetch, chunk, and parse.*
## Introduction
AI-powered search engines, chat assistants, and retrieval agents increasingly fetch web pages directly to answer questions, compare products, and cite sources. If your front end is hard to crawl, slow to render, or difficult to parse, those systems may skip your content entirely or extract it poorly.
Being AI agent-ready means serving clean, accessible, machine-readable pages from the first request. Your content should be available without unnecessary session barriers, easy to interpret through semantic HTML and structured data, and fast enough for bots that operate with short timeouts.
This guide focuses on the front-end implementation patterns that help AI agents access and understand your site reliably.
This work supports Generative Engine Optimization (GEO), which focuses on making your content easier for AI systems to retrieve, interpret, and cite in generated answers.
In practice, AI agent readiness is the technical foundation for GEO: if agents cannot fetch and parse your front end reliably, your content is much less likely to surface in AI-powered experiences.
## Infrastructure security (Preventing false positives)
Web Application Firewalls (WAFs) and rate-limiters are engineered to mitigate DDoS attacks and malicious scrapers.
However, default security rules frequently group legitimate AI agents into generic "Bad Bot" or "Unknown Scraper" categories, blocking them entirely.
### WAF customization
Configure your WAF to allow verified AI bots, not just requests that claim a bot-like `User-Agent`.
Treat the header as a hint, then verify it against published bot IP ranges, reverse DNS rules, or your provider's bot verification program.
Instead of hard-blocking high-frequency automated traffic from these sources, move them to a Managed Challenge (like Cloudflare Turnstile) rather than an interactive text/image CAPTCHA, which headless AI parsers cannot bypass.
### Verify the bot
`GPTBot`, `OAI-SearchBot`, `ChatGPT-User`, `ClaudeBot`, `Claude-Web`, `claude-user`, `PerplexityBot`, and `Google-Extended` are useful detection hints, but they are not identity proofs.
At a minimum:
- Match the request `User-Agent` against an explicit allowlist.
- Verify that the request originates from a published vendor IP range or a provider-level verified-bot program.
- Keep a separate fallback policy for unknown automation so spoofed browser traffic does not inherit the same bypass.
This matters because a middleware bypass that trusts the `User-Agent` string alone can be trivially spoofed.
### Explicit robots.txt configuration
Ensure your root directory explicitly grants access to generative engines.
Traditional Googlebot handles indexation for standard search, but specific tokens dictate LLM training and RAG ingestion.
Configure your robots.txt as follows:
```plaintext filename="robots.txt" copy
User-agent: GPTBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Claude-Web
Allow: /
User-agent: claude-user
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /
# Secure internal API routes and admin panels from scrapers
Disallow: /api/private/
Disallow: /admin/
```
## State-free middleware
Modern front-end applications frequently use middleware layer checks to detect cookies, manage geolocation routing, or enforce cookie-consent scripts before rendering page layouts.
However, AI agents usually operate with little or no durable browser state.
Training crawlers rarely preserve state, and even in-product browse tools that carry user context still frequently fetch pages without your expected session cookies.
If your front-end requires a valid session or cookie acknowledgment to return content, the bot will scrape a blank page or get caught in a redirect loop.
To fix this, write an exception rule directly into your edge/server middleware (such as the Next.js `middleware.ts`) that detects verified AI user-agents and bypasses client-side session or location checks.
Deliver your content in Prepr transparently on the initial anonymous HTTP GET request.
For example, in Next.js:
```ts filename="middleware.ts" copy
const AI_BOT_UA =
/GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-Web|claude-user|PerplexityBot|Google-Extended/i
function isVerifiedAIBot(request: NextRequest) {
const userAgent = request.headers.get('user-agent') ?? ''
if (!AI_BOT_UA.test(userAgent)) {
return false
}
// Pair the UA allowlist with IP verification, reverse DNS validation,
// or your WAF provider's verified-bot signal before bypassing auth logic.
return request.headers.get('x-bot-verified') === '1'
}
if (isVerifiedAIBot(request)) {
return NextResponse.next()
}
// Existing geolocation, consent, or session enforcement goes here.
return NextResponse.next()
}
```
Keep the allowlist small and explicit, and document that the strings can change over time. For OpenAI specifically, `ChatGPT-User` is user-triggered retrieval traffic rather than an autonomous crawler, so manage it in middleware or WAF policy rather than assuming `robots.txt` alone will control it.
## Optimize the ingestion layer (llms.txt)
Traditional search engines and AI agents use `sitemap.xml` for discovery.
For AI agents, you can complement the sitemap by giving them a condensed, machine-friendly summary of your most important content in one place.
Implement this with an `llms.txt` (and a more comprehensive llms-full.txt) at the root of your front-end repository (https://yourdomain.com/llms.txt).
An `llms.txt` file is plain Markdown. A practical structure is:
- An H1 with the site or product name
- A short blockquote that explains what the site contains
- Section headings for major content areas
- One link per page with a short description
For example:
```md filename="llms.txt" copy
# Prepr Documentation
> Complete documentation for Prepr CMS - a headless CMS for building personalized digital experiences.
## Pages
- [Prepr CMS Documentation - Headless CMS for modern applications](https://docs.prepr.io/index): Learn how to build fast, scalable applications with Prepr CMS. Complete guides for Next.js, Nuxt, Laravel, and more. Get started with our headless CMS today.
- [Quick Start Guide - Get Started with Prepr CMS in 15 Minutes](https://docs.prepr.io/quick-start-guide): Learn how to set up Prepr CMS, create content models, add content, and retrieve it using the GraphQL API. Perfect for developers new to headless CMS.
- [Changelog - Latest Updates and Features | Prepr CMS](https://docs.prepr.io/changelog): Stay up to date with the latest Prepr CMS features, improvements, and bug fixes. See what's new in our headless CMS platform.
- [Prepr CMS product roadmap - Upcoming features and improvements](https://docs.prepr.io/roadmap): Explore Prepr's product roadmap to see upcoming features, integrations, and improvements. Learn what we're building for the future of headless CMS.
- [Setting up your production-ready Prepr project](https://docs.prepr.io/project-setup): Learn how to set up your Prepr project for production with our comprehensive guide and essential resources.
- [Content modeling in Prepr CMS](https://docs.prepr.io/content-modeling): Learn how to set up schemas and models effectively in Prepr CMS for organized content management.
- [Connecting a front-end framework with Prepr CMS](https://docs.prepr.io/connecting-a-front-end-framework): Learn how to connect your favorite front-end frameworks to Prepr CMS for personalized content delivery.
- [Developing with Prepr CMS: guides and best practices](https://docs.prepr.io/development): Learn how to develop with Prepr CMS, including front-end frameworks, CI/CD, and integrations for a smooth development experience.
- [Guide to content management in Prepr](https://docs.prepr.io/content-management): Learn how to manage content, assets, localization, and collaboration in Prepr CMS effectively.
- [Data collection guides for Prepr CMS](https://docs.prepr.io/data-collection): Learn about data collection features in Prepr and how to set up tracking, recording, and managing visitor data effectively.
...
```
For Prepr projects, this file does not need to be static. You can generate it dynamically from Prepr content:
- Generate `llms.txt` from Prepr content in a route handler by querying published pages through the GraphQL API.
- Fetch a curated list of published docs pages from the Prepr GraphQL API in a route handler or edge function.
- Use editorial metadata such as title, summary, category, and slug to build the Markdown structure.
- Regenerate the output when content changes if needed, for example, by using Prepr webhooks to revalidate static pages.
## Zero-latency renders and Edge caching
AI scrapers are heavily optimized for processing speed; they will not wait for slow backend database operations or sluggish API responses.
Most AI crawlers enforce strict internal connection timeout limits (frequently between 2 to 5 seconds).
### Server-rendered and prerendered pages
Never rely on heavy client-side JavaScript fetching that serves a loading spinner for several seconds.
The page source must contain the full text content immediately upon the initial server response.
### Time to First Byte (TTFB) mitigation
Move your data fetching closer to the bot.
Use server-side or CDN caching to avoid repeated origin work for the same content.
Cache completely rendered HTML pages at the CDN edge so that when an AI bot hits a content URL, the response time is safely under 200ms.
Also keep canonical URLs direct and stable.
Avoid redirect chains longer than one 301 hop, and make sure each document resolves to a single canonical URL so retrieval systems do not split citations across duplicate paths.
## Enforce rigid semantic HTML
When an LLM scraper successfully fetches your page, it strips out styling and looks strictly at the document hierarchy to define context and determine which answers map to specific headings.
In your layout, map your content fields to explicit, native HTML semantic elements.
Never use styled `` or `
` tags to replace headings.
Ensure ``, ``, and `` tags follow a sequential hierarchy.
Isolate definitions, feature callouts, or summary takeaways using structural semantic tags like `` or ``.
To ensure agents get atomic chunks of content, keep continuous prose between heading tags bounded to roughly 200-400 words, or about 256-512 tokens for many common embedding pipelines.
This ensures that when external RAG applications slice your site layout into vector embeddings, your text naturally aligns with common token limits without breaking mid-sentence or losing contextual anchors.
Media matters here too. Use descriptive alt text, meaningful filenames, and visible captions where needed so multimodal retrieval systems can understand what an image contributes to the page.
## Dynamic JSON-LD structured data injection
AI agents use structured schema markup to bypass contextual guesswork.
It tells the agent exactly what a concept is, who wrote it, and when it was updated.
Dynamically inject detailed JSON-LD microdata on the server side using the metadata, such as your model and components in Prepr.
### Essential schemas for GEO
- `FAQPage`: Feeds precise question-and-answer pairs cleanly to LLM conversational interfaces.
- `Product`: Outlines features, structural specs, and clear-cut definitions.
- `TechArticle` / `Article`: Explicitly includes author, publisher, and dateModified. Because LLMs heavily prioritize fresh information, keeping the `dateModified` attribute completely accurate is vital for maintaining algorithmic relevance.
- `ImageObject`: Adds context for important diagrams, screenshots, product imagery, and other media that may be ingested separately from the surrounding page copy.
Also add a `rel="canonical"` tag to every content page and keep it aligned with the JSON-LD `mainEntityOfPage` or URL fields so duplicate routes do not dilute retrieval confidence.
## Implement GEO with Prepr
Prepr gives you most of the raw material for GEO already. The key is to map your schema cleanly into HTML, JSON-LD, and cache invalidation workflows.
- Map Prepr system and editorial fields directly into schema properties such as `headline`, `description`, `author`, `datePublished`, and `dateModified`.
- Use Prepr webhooks to trigger cache revalidation if you have static pages, so bots fetch fresh HTML shortly after editorial updates.
- Prefer field types with clear semantics when you model content. Rich text, titles, summaries, authors, publish dates, tags, and assets are easier to map into native HTML and structured data than large generic blobs.
As a rule of thumb, treat your Prepr schema as the source of truth for machine-readable meaning, not just for page copy.
## Eliminate hydration crashes
If you're using modern JavaScript frameworks (React, Next.js, Nuxt), the application compares server-rendered HTML against the client-rendered JavaScript layout when it loads—a process known as *Hydration*.
While a human user might ignore a subtle layout flicker caused by a hydration error, severe hydration mismatches can cause the entire JavaScript application to crash or unmount the Document Object Model (DOM) entirely.
If an AI agent attempts to run a JavaScript evaluation step on a broken page, it encounters a fatal layout error and leaves with zero data.
To solve this, monitor production error logs for hydration warnings. Ensure that dynamic, user-specific data (such as local time zones, system dark/light modes, or user login states) is wrapped in client-only wrappers that execute strictly after the initial page mount, keeping the baseline HTML completely intact for incoming crawlers.
Source: https://docs.prepr.io/ai-agent-ready-best-practices
---
# Prepr schema spec
*From this guide, you'll learn how to use the Prepr schema spec to generate and validate schema JSON files you can import into your Prepr schema.*
## Introduction
The Prepr schema specification is a standardized format (in JSON) that defines the structure, relationships, and rules of Prepr schema elements: *Models*, *Components*, *Enumerations*, and *Remote Sources*.
You can use this spec to validate manually created schema JSON files by running the [Prepr schema validation](/development/working-with-cicd/validating-a-schema).
You can also potentially use the Prepr schema spec to give additional context to an AI agent or a development tool if you wish to generate a Prepr schema automatically based on your UX design and instructions.
We’re excited to see how AI can accelerate the way you design your Prepr schema.
## Generate a Prepr schema (Experimental)
To automatically generate a Prepr schema using your preferred AI agent or development tool, perform the steps below.
1. Download the spec from our [Validation GitHub repo](https://github.com/preprio/action-schema-validation/blob/main/spec/2026-03-05.json5).
2. Upload the spec to your LLM and provide your project requirements.
3. Continue prompting to tweak fields and relationships.
4. Once the schema JSON files are generated, follow the steps in the [validation guide](/development/working-with-cicd/validating-a-schema) to upload them into a GitHub repo and validate them before importing them into your Prepr environment.
Source: https://docs.prepr.io/schema-spec
---
# AI text assistant features
*This article details the setup of the available AI assistant features for creating, editing and reviewing text in content items.*
## Introduction
Learn how to set up the following AI text assistant features to support editors with creating, updating and reviewing content:
- AI translation
- Custom prompting for text generation and text optimization
- AI text optimization with fixed list of actions
- AI text generation with predefined prompts
- AI check for SEO values
## Translating content items
Content editors can automatically create high-quality first drafts of content items in a chosen language using AI.

This feature lightens the workload for content editors who need to create translated copies of a content item.
This AI feature is enabled by default for all models and all text fields.
To disable AI translation for specific fields, follow the steps below.
1. In the **Schema** page, choose the model you want to update.
2. Click the applicable field to open the *Settings*, and go to the **AI features** tab.

3. Switch off the **Allow this field to be translated using AI** toggle.
Now when a content editor translates a content item for this model, this field will not be translated, but simply copied to the new language version.
## Customized prompting
Content editors can make an AI request to generate or optimize any text (text or dynamic content fields) in their content item by entering their own prompts.

This gives content editors more control over generating or optimizing their content.
To enable custom prompting for specific fields, follow the steps below.
1. In the **Schema** page, choose the model you want to update.
2. Click the applicable field to open the *Settings* and go to the **AI features** tab.

3. Enable the **Custom prompting** toggle.
Once done, content editors will see the **Ask AI** action when hovering over the enabled field and can choose the **Your request** option to enter their own prompts.
## Optimizing text
Content editors can use AI to improve existing text, for example, to make their text longer, shorter or simpler.

This feature makes it quicker and easier for content editors to create engaging text in their content.
To enable AI text optimizing for specific fields, follow the steps below.
1. In the **Schema** page, choose the model you want to update.
2. Click the applicable field to open the *Settings* and go to the **AI features** tab.

3. Enable the **Allow AI optimizing** toggle.
Once done, content editors will see the **Ask AI** action when hovering over the enabled field and the following Ask AI actions will then be visible:
- Improve writing
- Fix spelling and grammar
- Make shorter
- Make longer
- Simplify language
## Generating text
Content editors can auto-generate text with AI based on predefined prompts. For example, to create an SEO title based on the main title of an article.

This feature makes it quicker and easier for content editors to create engaging text in their content.
To enable AI text generation for specific fields, follow the steps below.
1. In the **Schema** page, choose the model you want to update.
2. Click the applicable field to open the *Settings* and go to the **AI features** tab.
3. Enable the **Allow AI text generation** toggle.
4. To set the prompt, it's easiest to choose one of the suggested prompts and adjust it for your own needs.

Once done, content editors will see a **Generate text** option when they click to **Ask AI** above the field they want to auto-generate with AI.
## Checking SEO values
The [*Content check*](/content-management/reviewing-content#content-check) feature automatically checks a content item for nonoptimal SEO values and makes suggestions you can choose to apply, adjust or ignore.

The *Content check* feature is enabled by default when you create a model.
It makes the icon available when editing a content item.
To disable this feature, follow the steps below.
1. Go to the **Schema** tab and choose the model you want to update.
2. Open the applicable model where you don't want content editors to use the *Content check* feature.
3. Click the **Settings** button to open the model settings.

4. Go to the **Features** tab and switch off the **Content check** toggle and click the **Save** button.
Now, content editors won't see the action when they open a content item for this model.
Source: https://docs.prepr.io/ai-text-assistant
---
# AI suggestions for adaptive content
*This article explains how to set up the AI assistant to generate suggestions for adaptive (personalized) content.*
## Introduction
When adding personalized content, editors and marketers can get AI suggestions for adaptive content based on a customer segment they choose.
The auto-generated variant suggestions gives them inspiration to quickly create and fine-tune their adaptive content.
## Setting up AI assistant to generate suggestions
To set up the AI assistant to allow editors and marketers to generate suggestions for adaptive content, follow the steps below.
1. Define context for AI requests in your [environment settings](/project-setup/setting-up-environments#define-environment-context-for-ai-requests).

2. Define additional context in each [segment](/personalization/managing-segments#defining-segment-context).

The AI icon is available by default in an adaptive content section. Once all the relevant context is defined, editors can [AI-generate suggestions for adaptive content](/personalization/managing-adaptive-content#generate-ai-personalized-variants).

If you have any questions, please [contact our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/ai-suggestions-adaptive-content
---
# AI-generated text for images
*This AI integration automatically generates text values, such as alt text, when uploading new images.*
## Introduction
When you activate the *Prepr image processing* integration, you can choose to integrate to AI to process images.
When a user uploads an image, Prepr CMS prompts AI to scan the image and the image file name to detect information and generate selected text values, such as alt text.
## Activate AI generation for image text fields
Simply activate the AI integration with the following steps:
1. Click the icon and choose the **Integrations** option to view all integrations.
Go to the **Prepr image processing** card and click the **Activate** button.

2. Enable the **AI** toggle and choose one or more of the fields listed.
- **Internal name**
- **Description**
- **Custom asset fields** - These include any custom text fields you added to the [*Asset* model](/content-modeling/defining-the-asset-model#add-fields-to-the-asset-model), like an *Alt text* field.
To make changes to the options, go back to the **Prepr image processing** card and click the **Manage** button to make your changes.
3. If you have any custom asset fields in the *Asset* model, set a prompt for each text field you want AI-generated, as follows:
- Go to the **Schema** page and click to open the **Asset** model.
- Click the text field you want AI-generated and in the dialog, click the *AI* tab.
- Then enable the toggle to **Allow AI text generation**
- Choose one of the *Suggested prompts* and edit it, if needed, or create your own and click the **Save** button.
Once the AI integration is activated and all prompts are set, editors will get generated text for any new images they upload.

If you have any questions, please [contact our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/ai-image-text
---
# Implementing an AI-assisted content migration
*This guide gives you a step-by-step process for migrating web pages, blog articles, and related content to Prepr CMS using AI to accelerate the repetitive work.*
## Introduction
The steps below are based on lessons learned during our content migration when we redesigned the schema for a new Prepr website.
Though it's a migration from one Prepr environment to another Prepr environment, these had very few direct one-to-one mappings between the old and new structure.
By running an AI-assisted migration project with a defined migration strategy, we could save time and effort migrating complex content structures.
## Prerequisites
The process below assumes the following is in place.
- A newly defined schema in the target environment for the new website.
- A mostly complete web app based on the new schema. In other words, low risk of needing to rework the migration.
- No content items in the target environment. This means, any sample content items to test the web app have been removed.
- Your AI coding assistant is set up with the [Prepr MCP server](/prepr-mcp-server/getting-started/claude-code) for both the old environment and the new environment.
Using your AI coding assistant with the Prepr MCP server lets you review content items in these environments when troubleshooting issues.
## Content migration strategy and AI interaction
Follow this high-level strategy for an efficient content migration.

This content migration strategy follows some ground rules:
- **Create empty content items first before filling in remaining fields**
Following this process reduces missing reference IDs.
- **Separate the migration into small, independent steps**
- **Don't over-engineer the migration**
Use lightweight technologies like a Node.js script in plain JavaScript to build the short-term migration script and avoid technologies like Typescript and NestJS which are more suited to long-term structured backend applications.
- **Create a mapper for each section in a [Stack field](/content-modeling/field-types#stack-field)**
The Prepr mutation API doesn't provide predefined types, so you cannot use AI to guess a mapping structure.
- **Include a detailed log such as a JSON report**
This format makes it easier for your AI coding assistant to analyze and help fix mapping issues.
## Example prompts
Adapt the following prompts to your schema, migration scripts, and coding assistant. Give the assistant access to the relevant schema files, source items, scripts, and dry-run reports before using them.
### Analyze schema to generate model mapping
Use this prompt to analyze the old and new schemas and generate the high level mapping between an old and new model:
```text copy
Compare the source schema in [SOURCE_SCHEMA_FILE] with the target schema in
[TARGET_SCHEMA_FILE]. Inspect the representative
source content items in the legacy environment: [LEGACY_ENV]
For every source model:
- Suggest the corresponding target model for each source model.
- Compare the field types, localization settings, validation rules, and allowed
nested components.
- Identify core fields that map directly, fields that need transformation, and fields
that have no target equivalent.
- Identify target fields that are required but have no source value.
- Flag references, assets, dynamic content, and Stack fields that need dedicated
handling.
Return a table of proposed mappings followed by a list of ambiguities and risks.
Do not invent a mapping when the schemas do not provide enough evidence. Ask me
to resolve each ambiguity before generating migration code.
```
### Generate model mapping
Use this prompt to create the initial model mapping file:
```text copy
Using the approved schema analysis and the source and target schema files, create
the model mapping for [SOURCE_MODEL_NAME]. Follow the structure and naming used
in [EXISTING_MAPPING_FILE].
Include the source and target model IDs, title fields, and slug behavior. Use only IDs and
field names found in the supplied schemas. Add unresolved cases to a separate
`questions` list instead of guessing.
Return the proposed JSON and briefly explain every mapping that is not one-to-one.
Do not modify any files yet.
```
After approving the model mapping, use this prompt to generate a mapper for a model or nested element:
```text copy
Create a mapper for [SOURCE_MODEL_OR_ELEMENT] using [EXISTING_MAPPER_FILE] as the
implementation pattern. Map it to [TARGET_MODEL_OR_ELEMENT] according to
[MAPPING_FILE].
Requirements:
- Preserve locale values and resolve references through [ID_MAP_FILE_OR_HELPER].
- Follow the existing payload shape and error-reporting conventions.
- Report unsupported fields or nested elements instead of silently dropping them.
- Keep the mapper focused on this model or element; do not change unrelated code.
- Add or update focused tests if this project has tests for the existing mappers.
Before editing, summarize the proposed field mapping and list any assumptions that
need my confirmation.
```
### Generate scripts
Use this prompt to generate the CLI scripts after approving the schema and model mappings:
```text copy
Create the CLI scripts for a two-phase content migration defined in
[MAPPING_FILE]. Follow the existing project structure and conventions in
[MIGRATION_DIRECTORY]. Use plain JavaScript and the existing Prepr API helpers;
do not introduce a new framework or abstraction layer.
Create these commands:
1. `create`: Fetch the eligible source items, create empty target items with their
title and slug values, and save every source-to-target ID mapping. Support
`--dry-run` and `--model `.
2. `fill`: Fetch the complete source content, resolve target IDs through the saved
ID mapping, call the model and nested-element mappers, and update the target
items in the `Review` stage. Support `--dry-run`, `--model `,
`--slug `, `--id `, and `--limit `.
For both commands:
- A dry run must not create or modify content or write an ID mapping.
- Validate required configuration before processing any items.
- Continue processing when one item fails, but return a non-zero exit status when
the run contains unexpected failures.
- Produce a structured JSON report with totals and item-level failures, including
the model, source ID, target ID when available, failure category, and message.
- Do not silently skip unmapped fields, references, or nested elements.
- Keep API access, ID-map storage, reporting, and model-specific mapping in
separate focused modules.
First inspect the referenced files and propose the files and functions you will
create or change. List any missing API details or mapping decisions instead of
guessing. After I approve the plan, implement one command at a time and show the
targeted dry-run command I can use to verify each one.
```
### Troubleshoot issues
Use a small dry-run report and the relevant mapper as context so the assistant can diagnose a specific pattern instead of making broad changes:
```text copy
Analyze the failures in [DRY_RUN_REPORT] for [MODEL_OR_ITEM]. Compare them with
[SOURCE_SCHEMA_FILE], [TARGET_SCHEMA_FILE], [MAPPING_FILE], and
[RELEVANT_MAPPER_FILES].
For each failure:
1. Identify the root cause and the affected source and target fields.
2. Classify it as a missing mapping, invalid transformation, unresolved reference,
unsupported nested element, invalid source data, or expected manual exception.
3. Recommend the smallest safe fix.
4. State how I can verify the fix with a targeted dry-run command.
Group repeated failures by root cause. Do not suppress errors or add fallback data
unless the target schema explicitly supports that behavior. Show me the diagnosis
before modifying any files.
```
After reviewing the diagnosis, use this follow-up prompt:
```text copy
Implement the approved fixes for [FAILURE_GROUPS]. Preserve the existing migration
behavior for all other models and elements. Then run the narrowest applicable dry
run or test, summarize the result, and list any failures that still require a
mapping decision or manual migration.
```
Source: https://docs.prepr.io/ai-assisted-content-migration
---
# The Prepr Next.js package
*The Prepr Next.js package offers some helper functions and the Prepr preview toolbar for easier personalization and A/B testing implementation.
This guide introduces you to the *Prepr Next.js package* and shows you how to use it.*
## Prerequisites
- You need to have a [Next.js project connected to Prepr](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-2-make-the-project-dynamic#connect-your-nextjs-website-to-prepr) before installing the package.
## Introduction
The *Prepr Next.js* package includes the following features:
- It provides API request headers for the following values:
- Each visitor's ID. You need this API request header, `Prepr-Customer-Id`, when you query adaptive content and content with A/B testing.
- Any UTM parameters, if applicable. This is useful to identify visitors who enter your website through a social media campaign, for example.
- HubSpot cookie, if it exists. This is useful for identifying visitors who are tracked in HubSpot as a lead, for example.
- The visitor's IP address. This is useful for localization.
- The Prepr preview toolbar includes the following features:
- Provides an easy way to test adaptive content and content with A/B test variants
- Allows content editors to edit content through a link from the preview page.

- The package also allows you to enable Visual editing in your Prepr environment. Check out the [visual editing guide](/project-setup/setting-up-previews-and-visual-editing) for more details.
- The package auto detects stega-encoding to prevent misaligned layout elements in the preview version of the web app.
For additional implementation options and technical details, check out the [Prepr Next.js package repo](https://github.com/preprio/prepr-nextjs) directly in GitHub.
## Installation
To install the *Prepr Next.js package,* follow the steps below.
1. Run the following command in your Next.js project:
```bash copy
npm install @preprio/prepr-nextjs
```
2. Add the `PREPR_ENV` variable to the `.env` file. You can enable the Prepr preview toolbar for a staging environment by setting the value to `preview`.
```bash copy filename="./.env" {2}
PREPR_GRAPHQL_URL=
PREPR_ENV=preview
```
3. To manage request headers for adaptive and A/B test content you need to implement the `createPreprMiddleware` function. Go to your `proxy.ts` or the `proxy.js`
file and add the code below. If you don't have this file, you can create it in the root of your project.
**Behind the scenes**
- The `createPreprMiddleware` function accepts a request and optional response property and returns a `NextRequest` object.
It does this so you can chain your own middleware to it.
- The `createPreprMiddleware` function checks every request if the `__prepr_uid` cookie is set. If it isn't, the function generates a new UUID and sets the cookie with this new value.
Then it returns a `Prepr-Customer-Id` header with the value of the `__prepr_uid` cookie to simplify your personalization and A/B testing implementation.
- If the `PREPR_ENV` environment variable is set to `preview`, the `createPreprMiddleware` function also checks for searchParams `segments` and `a-b-testing` in the URL.
If these searchParams are set, it sets the `Prepr-Segments` and `Prepr-AB-Testing` headers with the values of the searchParams, and stores its value in a cookie.
Now that you've successfully installed the package, let's see how to use it.
## Usage
### Setting your API request headers
When requesting adaptive content or A/B testing content you need to send an ID for the visitor in the request header.
The `getPreprHeaders()` helper function simplifies this by setting the API request headers for you. Simply call the `getPreprHeaders()` helper function like in the example code below for a typical web page.
It returns an list of headers that you can include in the request.
### Installing the Prepr preview toolbar component
The Prepr preview toolbar is a small component that appears as the Prepr icon in the page of your preview website.
It allows editors to quickly switch between A/B testing variants and personalization segments and to select a section of the page to open the corresponding content item directly in Prepr.
The instructions below show you how to set it up.
The component fetches all segments from the Prepr API.
So, you need to give it access to do this as follows:
1. In your Prepr environment, click the icon and choose the **Access tokens** option to view all the access tokens.
2. Click the *GraphQL preview* access token to open it and tick the **Enable edit mode** checkbox and click the **Save** button.


Now that you've set up the access for the preview bar, you can display the Prepr preview toolbar component as follows:
1. Navigate to your root layout file, this is usually `layout.tsx`. Then add the following code to your layout file.
```ts filename="layout.tsx"
import { getToolbarProps } from '@preprio/prepr-nextjs/server'
import {
PreprToolbar,
PreprToolbarProvider
} from '@preprio/prepr-nextjs/react'
import '@preprio/prepr-nextjs/index.css'
export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const isPreview = process.env.PREPR_ENV === 'preview'
const toolbarProps = isPreview ? await getToolbarProps(process.env.PREPR_GRAPHQL_URL!) : null
return (
{/*...*/}
{isPreview && toolbarProps ? (
{children}
) : (
children
)}
)
}
```
Now the Prepr preview toolbar component is rendered on every page of your website.
This component shows the segments in a drop-down list and a switch for A and B variants for an A/B test.
By adding the `getPreprHeaders()` function to your API calls like you did in the [previous section](/prepr-nextjs-package#setting-your-api-request-headers), it automatically updates the adaptive content and A/B testing variants when you select a new segment or variant.


## All done
Congratulations! You've successfully simplified personalization and A/B testing, and installed the Prepr preview toolbar in your Next.js website.
This brings you to the end of the setup guide for the *Prepr Next.js* package.
Don't hesitate to give us feedback on your experience using this guide.
## Next steps
To learn more on how to expand your Next.js project, check out the following resources:
- [More data collection details](/data-collection)
- [More about A/B testing](/ab-testing)
- [More about personalization](/personalization)
Source: https://docs.prepr.io/prepr-nextjs-package
---
# Prepr Toolkit
*The Prepr Toolkit is a framework-agnostic TypeScript library that provides preview functionality, visual editing, and front-end setup for Prepr CMS personalization and A/B testing.
It's compatible with React, Next.js, Nuxt, Astro, and SvelteKit.*
Source: https://docs.prepr.io/prepr-toolkit
---
# GraphQL API
The Prepr GraphQL API is a read-only API based on the GraphQL language.
The GraphQL API offers more precise and flexible queries than the REST API. You can precisely define the data you want and get this data with only a single call instead of multiple REST requests.
All Prepr environments have a GraphQL schema generated from associated content models. The schema is generated dynamically at request time. This ensures that changes to the schema design are instantly reflected in your web application.
If you need any assistance, you're welcome to reach out to us:
[Join our Slack community](https://slack.prepr.io) or
[Reach out to our support team](https://prepr.io/support).
Source: https://docs.prepr.io/graphql-api
---
# Prepr MCP server
*The Prepr MCP server connects AI clients to your Prepr environment through the [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro).
It gives AI tools a safe, structured way to search content, create and update localized items, review publication state, manage workflow, and publish or unpublish content directly in Prepr.*
Source: https://docs.prepr.io/prepr-mcp-server
---
# Mutation API Reference
The Prepr REST API is a Content Delivery and Mutation API.
All responses are cached by our API CDN, caches will be cleared if anything changes in your Prepr environment. Contact support@prepr.io to get help implementing your application, or join our development [Slack](https://slack.prepr.io).
Source: https://docs.prepr.io/mutation-api
---
# Develop with Prepr
Experience how easy it is to develop with Prepr CMS.
Source: https://docs.prepr.io/developing-with-prepr
---
# Stay updated
We aim for full transparency and are always here to help out. Explore our newest features and [learn about improvements we're working on](/roadmap).
Source: https://docs.prepr.io/stay-updated
---
# Step-by-step setup guide
## Introduction
In this guide, you'll set up a production-ready project for a typical use case scenario where you have two environments, one for Development and one for Production.
Make the most out of one of the Prepr [paid subscription plans](https://prepr.io/pricing/calculator?hutk=e939c72e0a6a2465ef3afd4d59dac5db\&is_annual=true) to create these environments. Alternatively, [sign up for a free Prepr account](https://signup.prepr.io/) to create one environment. As the owner of the account you can [manage your subscription in Prepr](/project-setup/managing-your-subscription).
## Step 1: Create environments
In this step, the *Owner* creates the environments needed for the project. Check out the [Manage environments doc](/project-setup/setting-up-environments) on how to create an environment.
## Step 2: Add users
Consider a simple strategy for user roles where the *Owner* makes use of the [default user roles in Prepr](/project-setup/managing-roles-and-permissions#default-roles) to create user profiles for the following types of users:
- *Admin* users - For administrative users to manage environments and user profiles. Once the *Admin* user profiles have been added, these users can log in and create the rest of the users who need to have access to Prepr.
- *Developer* users - For content modelers who will manage the *Schema* and developers who will manage developer settings like *Access Tokens* and *Integrations*.
- *Editor* users - For content editors who will create and manage all the content.
Check out the [Manage users doc](/project-setup/managing-users) for more details on how to create users and resend invitations.
## Step 3: Add roles and permissions (optional)
If your users need to be grouped with a different set of permissions separate from the [default user roles available in Prepr](/project-setup/managing-roles-and-permissions), you can [add custom user roles](/project-setup/managing-roles-and-permissions#add-or-edit-roles), if your subscription plan allows.
## Step 4: Set up SSO (optional)
If your project strategy for authorization includes implementing SSO (single sign-on) for users to log in to Prepr follow the [SSO setup guide](/project-setup/setting-up-sso) to complete this set up, if your subscription plan allows.
## Step 5: Migrate content (optional)
If you are replacing an existing system with Prepr and have lots of important content that you want to migrate to Prepr, follow the steps in the [migration guide](/project-setup/migrating-content) to ensure a successful migration of the existing content to Prepr.
## Step 6: Integrate systems (optional)
You can set up integrations between Prepr and external systems. Check out the list of [prebuilt integrations](/integrations) that you can activate from within Prepr.
Alternatively, for custom requirements, consider the following options for an integration solution:
- Mutation API: Use the [Prepr REST API](/mutation-api) to update content items, assets, segments or visitors with data from another system.
- Remote source: [Set up a custom remote source](/content-modeling/creating-a-custom-remote-source) to automatically include content from an external system in your content items in Prepr.
- Webhooks: [Manage webhooks](/development/best-practices/webhooks) to use events in Prepr to trigger actions such as automated notifications, deployment or data caching operations.
## What's next?
Now that all the preparation steps are done, you can continue implementation in Prepr with creating a schema and the related content.
Follow the [Model content docs](/content-modeling) to model content and create a schema in Prepr.
When the schema is completed, follow the [content](/content-management), [asset](/content-management/managing-assets), [localization](/content-management/localizing-content) and the [collaboration](/content-management/collaboration) guides to create and manage content in Prepr.
Source: https://docs.prepr.io/project-setup/step-by-step-guide
---
# General overview of Prepr
Before jumping into how to use Prepr, it's important to understand the high-level structure of the CMS. See the image below for a typical use case scenario where you have two environments, one for Development and one for Production.

As shown in the image above, Prepr is made up of the following parts:
## Organization
At the top level, the *Organization* represents your company or a customer's company.
An organization typically consists of one or more web applications and contains the key features below.
- [Environment management](/project-setup/setting-up-environments) - Create or update environments according to your DTAP strategy.
- [User management](/project-setup/managing-users) - Create or update **Users** in multiple environments and manage roles for your organization.
- [Subscription management](/project-setup/managing-your-subscription) - As the *Owner* of the Prepr account, you can manage the subscription plan.
- [Audit log](/project-setup/audit-log) - View user activities in each environment to help troubleshoot errors.
## Environments
An *Environment* is an isolated container for your project and an organization can have one or more environments. A front-end app is usually connected to one Prepr environment. For more details, check out [the environments doc](/project-setup/setting-up-environments).
Most of the setup for a typical project is done at this level including *Access tokens*, *Users*, *Locales*, *Integrations*, etc.
## Schema
A *Schema* is your content structure. Check out the [introduction to a schema](/content-modeling/fundamentals) to learn more about a schema with its *Models*, *Components* and *Remote sources* and how they relate to each other.
Every model, component and remote source has *Fields* defined. Learn more about fields in the [field types doc](/content-modeling/field-types).
## Content
Content contains all the content items for a particular environment. This section is where content editors [manage content items](/content-management) based on the structure defined in the *Schema*, [localize content](/content-management/localizing-content) and [collaborate with other content editors](/content-management/collaboration) to publish quality content.
## Media
*Media* houses all assets in the Prepr Media Library. This is where editors [manage different types of assets](/content-management/managing-assets) like images and videos for relevant content.
## Segments
*Segments* allow you to group visitors by common characteristics and similar behavior when they interact with your front-end apps.
Use segments to set up target audiences for [personalization](/personalization/setting-up-personalization). Check out the [segments doc](/personalization/managing-segments) to learn how to set up segments and manage your visitors.
## What's next?
Now that you understand the high-level structure of your project, follow the [setup steps](/project-setup/step-by-step-guide) to set up your production-ready project.
Source: https://docs.prepr.io/project-setup/prepr-overview
---
# Setting up environments
*A user can belong to multiple organizations and an organization can have one or more environments. This article explains what an environment is and how to use environments in Prepr.*
## Introduction
An environment is an isolated container for your project, for example for a website or an app. An environment contains the schema, content items, media files, segments and users. When you have multiple environments, it allows you to safely test your project without affecting your production-ready content. Let's look at how to do this in more detail.
## Create an environment
If you haven't already done so, go to https://signup.prepr.io/ and sign up for a Prepr account.
1. After you sign up, you'll be prompted to add an environment.

2. Enter a meaningful **Name** for your environment. The **URL** will be generated automatically based on the name you entered.
3. Choose a **Default locale** for your project. Learn more about how to use locales in the [Localization guide](/content-management/localizing-content).
4. Set the **Development stage** to either *Development*, *Testing*, *Acceptance* or *Production* according to your [DTAP configuration](#set-up-a-dtap-configuration).
5. Click the **Add environment** button to confirm the settings.
Next, you’ll be prompted to load Prepr demo data or start from scratch.

- **Load Acme Lease demo**. If you’re exploring Prepr we recommend loading demo data from the *Acme Lease* example. It includes examples for common use cases, like a blog and landing pages. Also, you'll get sample segments to try [A/B testing](/ab-testing/setting-up-ab-testing) and [Personalization](/personalization/setting-up-personalization) features in Prepr.
- **Start from scratch**. If you’re setting up a Prepr environment with a specific project in mind, choose to start from scratch. This allows you to add models, components, and fields yourself.
Once you've made your choice, the environment is now ready for you to use. Check out the [Users](/project-setup/managing-users) and [Roles and permissions](/project-setup/managing-roles-and-permissions) docs to manage additional users.
## Set up a DTAP configuration
Many development teams use a DTAP (Development, Test, Acceptance and Production) configuration to manage their development, testing, acceptance and deployment to production. One of the first things that you need to decide before starting your project is your DTAP strategy. Your DTAP strategy will define how many environments you need to create.
## Environment setup for multiple brands
Prepr offers specific features to support multiple brands in an organization. We recommend that you create an environment per brand and manage your schema and content using the following features:
- Shared schema
- Shared content
### Shared schema
Different brands often need different content, but it makes sense for an organization to keep the structure of all the content the same across their brands.
In this case, it's useful to use the same schema across multiple environments.
That's what we call a shared schema.
This will also simplify and speed up development by enabling a leaner and more consistent code base.
To create a *Shared schema* do the following:
1. Click the environment dropdown at the top right, choose your organization and click to open the environments overview.
2. Click the **Shared schema** tab to open the *Schema Editor*.

Now you can create models, components, enumerations and remote sources that will be shared by multiple environments within your organization. Check out the [Create schema docs](/content-modeling) for more details.
### Shared content
Sometimes, different brands want to share content within the same organization.
For example, each brand creates their own articles, but these articles belong to common categories.
This can be done with shared content.
To enable sharing of specific content, enable the toggle **Allow items from all environments** when you add the [Content reference field](/content-modeling/field-types#content-reference-field) to a model.

Check out the [Create schema docs](/content-modeling) for more details.
## Manage Environment settings
You can update your environment settings by clicking the icon and choosing the **General** option.

### General settings
In the *General* section, you can rename your environment, change the **Timezone**, **Interface language**, **Development stage**, and generate the **AI context**.
#### Define environment context for AI requests
If your implementation team enables any AI features, generate some context about the environment purpose in the **AI context** section:

1. Click one of the buttons, **Claude**, **Gemini**, or **ChatGPT** to open up your preferred agent automatically or
click the button to copy the prefilled prompt in your environment settings and paste it into your preferred AI tool to request it to generate relevant environment context.
2. Copy the detailed context response from your AI tool and click the **Paste** button to fill the **AI context** field.
3. Once done, review the context for accuracy and click the **Save** button.
The AI model uses this context to tailor AI requests such as the [AI text assistant features](/ai-text-assistant).
Our AI requests use this context description to ensure outputs are consistent with the brand, audience, and overall purpose of your environment, thereby maximizing accuracy and relevance.
{/* Make use of the example below to help you define the context for your environment.
```md copy
# Organization Overview: Acme Lease
## Identify the Organization
- **Name:** Acme Lease
- **Type:** Local business/Automotive
- **Primary Language:** English
## Collect Information
### Basic Details
- **Brand Name:** Acme Car Lease
- **Industry:** Passenger car leasing
- **Legal Name:** Acme Car Lease NL
### Operational Details
- **Mission:** To provide the simplest, most transparent, and most flexible passenger car leasing experience, making it effortless to drive the perfect vehicle for their lives.
- **Vision:** To be the most trusted and preferred local partner for accessible, hassle-free vehicle mobility, leading our region in the transition to smarter, more sustainable driving solutions.
- **Core Values:** Trust and transparency, local focus and care, simplicity and forward mobility
- **Brand:**
### Products and Services
- **Main products** Passenger cars, electric passenger cars, classic passenger cars
### Geographic Presence
- **Location:** Netherlands
- **Operational Hours:** Daily 24 hours
- **Main Distribution Channels:** Website
- **Market Presence:**
### Competitive Edge and Strategy
- Acme Car Lease is the most trusted, local partner for future-ready mobility.
- We combine personalized, transparent service with specialized expertise in electric vehicle leasing and regional incentives that national brokers cannot match.
## Contact Information
- **Phone:** +31 11 111 111
- **Email:** [hi@acmecarlease.nl](mailto: hi@acmecarlease.nl)
- **Website:** [Acme Car Lease](https://acme-lease.prepr.io/)
- **Social Media:**
- [Facebook](/...)
- [Instagram](/...)
``` */}
### Content settings
Apart from the general environment settings you can also manage some content-related settings defined below.
#### Workflow stages
You can add custom workflow stages to each of the existing Prepr workflow stages to align the collaboration workflow with your own content creation process.
For example, when you have translation tasks for content items, you could add a stage like *Translate* to the *In progress* stage.

To add a custom workflow stage, follow the steps below:
1. Click the icon and choose the *General* option.
2. Scroll down to the *Workflow stages* in the *Content* section and click the icon next to the related workflow stage.
3. Then, give your custom workflow stage a name and choose a color to represent that stage in the content pages.
#### Content tree parent slug format
To get the best possible result in the *Content tree* layout for your editors, you need to define the standard format of the parent content item slug values.
To do this, go to the **Parent slug format** setting, and simply choose one of the values to use a predefined *Regex* expression or add a **Custom** expression.

- **Not set** - This default value means the content tree layout is not available to content editors.
- **No slashes** - Choose this value if the topmost parent content items never have slashes (`/`) in the slug.
- **Maximum 1 slash** - Choose this value if the topmost parent content items have no slashes or one slash.
- **Maximum 2 slashes** - Choose this value if the topmost parent content items have no slashes, one slash or two slashes.
- **Custom** - Create your own regex expression to match your standard format of the parent content item slug.
To see the results of your definition, navigate to the *Content* page and choose the [Content tree layout](/content-management/managing-content/managing-content-items#content-tree)
#### Required field validation
The default drop-down value is set to **Done**, but you can choose another group of workflow stages to trigger the required field validation. For example, when a content item is moved to **Review** or **Done**.
If your team implements [conditional field visibility](/content-modeling/field-types#common-settings), you can also define whether Prepr needs to check required conditional fields when they are hidden with the **Ignore required fields with conditional visibility** toggle.
When enabled, you allow a content item to be saved and published when a required field is hidden.
#### Visual Editing visibility
This dropdown allows you to choose when you can [preview content items](/project-setup/setting-up-previews-and-visual-editing) directly in the content detail page.
The default drop-down value is set to **To do** **In progress** **Review** **Done**.
Source: https://docs.prepr.io/project-setup/setting-up-environments
---
# Setting up previews and Visual Editing
*This article explains how to set up previews and visual editing for content editors to see their changes in real-time in a new tab or with a side-by-side view directly in Prepr CMS.*


## Setting up previews and Visual Editing for web pages
And that's it. Whenever a content editor edits a content item for a model with visual editing enabled, they can see their changes real-time with the side-by-side view.
## Troubleshooting
Answers to some common questions about Visual Editing.
**Why do I see the "Visual Editing not available yet" screen instead of the live preview?**
- This means the Visual Editing has not been enabled on the related model for this content item.
To enable Visual Editing for this model, make sure to follow all steps when [setting up the preview URL](#set-up-preview-urls).
**Why can't I see my content item changes in the live preview?**
- This could mean the preview URL points to a production web app. If you enable Visual Editing for a production preview URL, then changes will not be visible until the user publishes the content item.
[Update the preview URL](#access-unpublished-content) to one for a staging web app that uses the *GraphQL Preview* access token to see any unpublished changes in the live preview.
**Why do I get the server error `Refused to frame 'https://...' because an ancestor violates the following Content Security Policy directive...`?**
- This error means that iframes are not allowed for the Prepr domain. To enable them, update the [Content Security Policy in your web server](#enable-live-preview-in-your-front-end).
**Why are the *Segment* and *A/B test* switches not visible in Visual Editing?**
- [Install the Prepr Toolkit](https://github.com/preprio/prepr-toolkit) in your front end to enable them. If you don't have a JavaScript-based web framework, [follow the instructions](#enable-segment-and-ab-test-switches) above to activate the switches without using the Prepr Toolkit.
**Why are there invisible unicode characters in the preview API response?**
- These are Stega-encoded strings which produce invisible output to allow content editors to use the [Vercel **Edit Mode**](#activate-edit-mode-in-your-front-end).
When you turn on edit mode in an access token, Stega-encoding serializes metadata into invisible UTF-8 encoded characters and appends them to string values.

**Why does Visual Editing appear blank or load without styling when using Vercel Deployment Protection?**
- This happens because `x-vercel-protection-bypass` only applies to the initial page request. Follow-up requests for assets like CSS and JavaScript are still blocked, causing the preview to appear broken.
- To fix this, add both parameters to your preview URL so a bypass cookie is set and used for all requests:
`?x-vercel-protection-bypass=&x-vercel-set-bypass-cookie=samesitenone`
- The `samesitenone` value is required when Visual Editing runs in an iframe, otherwise the cookie may not be sent, and assets will still fail to load. See the Vercel docs on [Protection Bypass](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection) and [Protection Bypass for Automation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation#using-protection-bypass-for-automation) for more details.
If you have any other questions, don't hesitate to [contact our Support Team](https://prepr.io/support).
Source: https://docs.prepr.io/project-setup/setting-up-previews-and-visual-editing
---
# Architecture scenarios
Discover advanced Prepr CMS features tailored for more complex project structures and organizations.
Source: https://docs.prepr.io/project-setup/architecture-scenarios
---
# Migrating content to Prepr
*From this guide, you’ll learn how to prepare and migrate your content from another CMS or legacy content system to Prepr using the REST API.*
## Introduction
Content migration is the process of moving content from one content management system to another.
Follow the steps below to migrate content from a CMS or any other external database to a headless setup in Prepr.
- [Step 1 Analyze existing content](#step-1-analyze-existing-content) - In the first step, the data analyst identifies data cleanup activities, content structure discrepancies or gaps and defines a content migration audit checklist.
- [Step 2 Design mapping rules](#step-2-design-the-mapping-rules) - Based on the analysis results and the content structure in Prepr, design mapping rules to transform the content to Prepr content.
- [Step 3 Create the migration script](#step-3-create-the-migration-script) - Use the mapping rules from the previous step to extract data from the legacy system and transform this data to a Prepr content structure. Use the Prepr REST API to load the transformed content into Prepr.
- [Step 4 Verify and migrate content](#step-4-verify-the-migrated-content) - Use a combination of automated auditing, manual checks and system testing to confirm that the content was migrated successfully.
## Step 1: Analyze existing content
It's essential for a data analyst to analyze the existing content before moving it to a new system. The results of this analysis will provide insights into necessary data cleanup activities, content structure and an audit checklist. Here is a list of key points answered during this step:
- **Identify any other related external systems** - For example, where data lookups are referenced.
- **Identify the set of data to be migrated** - The scope of all content that needs to be migrated can be defined with the following criteria:
- **Type of content**, for example, pages, articles, authors, etc.
- **Age of the content to be migrated**, for example, it might not be necessary to migrate very old or archived articles.
- **The number of content items**
- **Structure of content items**, i.e., what fields are included in a content item like title, slug, description, media, etc. and the corresponding validation rules, for example, whether they are mandatory or other limits.
- **Technical analysis of the content items**, such as API documentation.
- **Identify any necessary data rules**
- **Identify the requirements for data cleansing** - Identify outdated and low-performing pages, broken links and redirects, and missing page elements such as tags, categories, meta descriptions, etc. This way, you can identify how to clean up the content and simplify the migration process.
The content modeler can use the results from this analysis when modeling the new content structure for the front-end implementation. Check out the [Design schema guide](/content-modeling/fundamentals) and [UX patterns](/content-modeling/examples) for practical suggestions on designing a scalable content structure for your web app. To continue the next step for content migration, you'll need a completed Prepr schema, at least modeled if not yet in Prepr.
## Step 2: Design the mapping rules
Now that you have the results of the analysis of the existing content, you can design mapping rules according to the new content structure for your web app.
At a high level, look at the table below for suggestions on how to map data types from your old structure to field types in Prepr.
**General field types**
| Data type | Prepr field type |
|-------------------------|-------------------------------|
| Text entry | **[Text](/content-modeling/field-types#text-field)** |
| Media files | **[Assets](/content-modeling/field-types#assets-field)** |
| URL path | **[Slug](/content-modeling/field-types#slug-field)** (For a URL path to a specific content item.) |
| Boolean value | **[Boolean](/content-modeling/field-types#boolean-field)** |
| Predefined list | **[List](/content-modeling/field-types#list-field)** |
| Integer or float | **[Number](/content-modeling/field-types#number-field)** |
| Date and time | **[Date and time](/content-modeling/field-types#date-and-time-field)** |
| Tags, keywords | **[Tags](/content-modeling/field-types#tags-field)** |
| Coordinates | **[Location](/content-modeling/field-types#location-field)** |
| Social media post embed | **[Social](/content-modeling/field-types#social-field)** |
| HEX color code | **[Color](/content-modeling/field-types#color-field)** |
**Special field types**
Prepr supports special field types that let you create feature-rich web pages and deliver personalized content to your end users. For more details, see the table below.
| Prepr field type | Capability |
|-------------------------|-------------------------------|
| **[Stack](/content-modeling/field-types#stack-field)** | Allows you to create a stack of components and models for feature-rich web pages. And the in-built Personalization feature lets you deliver the right content to your audiences. |
| **[Dynamic content](/content-modeling/field-types#dynamic-content-field)** | Allows you to combine various elements. Those elements can include text, headings, lists and even components. This field is often used for (blog) articles that contain all kinds of elements. |
| **[Remote content](/content-modeling/field-types#remote-content-field)** | Allows you to reference content in a remote source. |
| **[Content reference](/content-modeling/field-types#content-reference-field)** | Allows you to create a link to another content entry. Please note each referenced content must be set up as a separate content item in Prepr.|
| **[Component](/content-modeling/field-types#component-field)** | Allows adding a custom set of fields to your model. |
See an example below of mapping rules of an article and the article author from a legacy CMS to Prepr. This mapping is based on the *Article* and *Person* models from the [*Blog* pattern](/content-modeling/examples/blog). You can also see the schema in a Prepr environment with Demo data or create a model and choose the *Blog* template.
**Person mapping**
|Legacy field| Legacy field type | Prepr field| Prepr field type |Additional rules|
|------------|-------------------|-----------|-------------------|-----------------|
|AUTHOR.display\_name|name|`full_name`|Text| Required |
**Article mapping**
|Legacy field| Legacy field type | Prepr field| Prepr field type | Additional rules|
|------------|-------------------|-----------|------------------|-----------------|
|headline|title|`title`| Text |Required. |
|description|summary|`excerpt`| Text|Map as HTML to include styling. |
|IMAGE |ImageObject |`cover`| Asset|Upload this asset in Prepr first and link with Prepr generated `id`.|
|datePublished|datetime|`publish_on`| Date and time |Parse value to UNIX Timestamp.|
|AUTHOR| Person|`authors`| Content reference |Create the *Person* content item first and link with Prepr generated `id`. |
|body|long-text|`content`| Dynamic content|Map as HTML to include styling.|
Use the JSON examples below to clarify the mapping for the content migration developer to transform and load data in Prepr with the correct hard-coded values and the matching Prepr REST API request structure.
**Person JSON mapping example**
```json copy
{
"model": {
"id": {{Prepr Person model ID in the target environment}}
},
"publish_on": {
"en-US": {{Current date time in UNIX Timestamp}}
},
"locales": [
"en-US" // List of available languages for this content item.
],
"workflow_stage": {
"en-US": "Done"// The status for this content item locale entry
},
"items": {
"en-US": { // The locale of a specific content item entry.
"full_name": {
"label": "Text", // API label for a Text field
"body": {{AUTHOR.display_name}}
}
}
}
}
```
**Article JSON mapping example**
```json copy
{
"model": {
"id": {{Prepr Article model ID in the target environment}}
},
"publish_on": {
"en-US": {{datePublished}}
},
"locales": [
"en-US"
],
"workflow_stage": {
"en-US": {{"Done" if datePublished is not empty, else "In progress"}}
},
"items": {
"en-US": {
"authors": {
"label": "Publication",
// API label for a Content reference field
"items": [
{
"id": {{The auto-generated ID of the created Person}}
}
]
},
"content": {
"label": "ElementBox",
// API label for a dynamic content field
"items": [
{
"label": "Text",
"body": {{body}}
}
]
},
"cover": {
"label": "Asset", // API label for an Asset field
"items": [
{
"id": {{The auto-generated ID of the uploaded asset}}
}
]
},
"excerpt": {
"label": "Text",
"body": {{description}}
},
"title": {
"label": "Text",
"body": {{headline}}
}
}
}
}
```
## Step 3: Create the migration script
Depending on your content migration strategy, the available legacy tools and technical solution, this process could be one or more scripts or programs to do the following:
1. Extract the content from the legacy system that matches the scope criteria defined in [Step 1](#step-1-analyze-the-legacy-content).
2. Transform the content using the mapping rules from [Step 2](#step-2-design-the-mapping-rules) and prepare it for the load process.
3. Load the transformed content using the [Prepr REST API](/mutation-api).
Once you’ve transformed the content, it’s time to load it in Prepr using the Prepr REST API. Load the content in the following order:
**1. Upload assets** - This is necessary if the [Prepr Media Library](/content-management/managing-assets/introduction-to-assets) will be the source repository for all the images, videos and files linked to your content. When an asset is uploaded successfully, you can then use the `id` generated by Prepr to link to it when you create the corresponding content item. Alternatively, you can include a `reference_id` on the asset for an external ID value. Check out the [REST API Manage assets doc](/mutation-api/assets-upload-update-and-destroy) for more details.
**2. Create referenced content items** - If you have content that references other content items, for example, an *Article* that references an *Author*, then it's important to create the child content item first, the *Author* in this case. When the child content item is created successfully, you can then use the `id` generated by Prepr to link it to the parent content item that you will migrate afterwards. Check out the [REST API Create content items doc](/mutation-api/content-items-create-update-and-destroy#create-a-content-item) for more details.
**3. Migrate remaining content items** - Once all the content dependencies have been created in Prepr, create the remaining content items using the same REST API endpoint that you used in the previous instruction.
Once it's ready, run the script in a *Test* or *Development* environment to prevent affecting production-ready content. Learn more on how to [set up a DTAP configuration in Prepr](/project-setup/setting-up-environments#set-up-a-dtap-configuration).
Move on to the next step to verify and migrate the content to production.
## Step 4: Verify and migrate content
That’s it. The hardest part is over. After the test migration is completed, ensure all the data is moved to Prepr correctly by checking the reported totals and error logs. If you’ve already connected your front end, you can also verify the content in your web app.
In case there are any inconsistencies, check and update the existing data according to the audit requirements or adjust your migration script. Once you're satisfied with the testing results, run the script in the *Production* environment.
If you still have questions or need assistance, please [contact our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/project-setup/migrating-content
---
# Managing users and profile settings
*This guide shows you how to manage users in an environment or organization when you're the administrator or owner of the Prepr account.
Additionally, you can learn how to manage your own account profile as a standard Prepr user.*
## Managing user accounts
There are two ways to manage user accounts: on the organization level and on the environment level.
A user can have other different roles in separate environments.
For example, the *Admin* role in one environment and the *Editor* role in another.
Check out the [Roles and permissions doc](/project-setup/managing-roles-and-permissions) for more details about *Role-based access control* for users.
We recommend managing your user accounts on the organization level.
### Add users - organization level
To create a user account on the organization level, complete the following steps:
1. Click the environment dropdown at the top right, choose your organization and click to open the environments overview.
2. Go to the **User management** page to open a list of all users in the environments of your organization.
3. Simply click the **Add User** button and fill in the user details. The *First name* and the *Email address* are required.
4) Choose the *Language* of this user (default English) and choose the [*User expiration date*](#define-user-expiration-date) for this user account, if needed.
5) And lastly, choose the requested *Environment* and the corresponding *Role* of this user. For more details, check out the [Managing user roles doc](/project-setup/managing-roles-and-permissions). Choose at least one role per environment.
When saving a new user, an invitation email will be sent to the given email address. When saving an existing user, their account will be updated immediately with the changed permissions or roles.

### Add users - environment level
To create a user account in the environment directly, follow the steps below.
1. Go to **Settings → Users** to open a list of all users in this environment.
2. Simply click the **Add user** button and fill in a few user details. The *First name* and *Email address* are required.
3. Choose the language of this user (default English) and the [*User expiration date*](#define-user-expiration-date) for this user account, if needed.
4. Finally, choose the *Role* for this user. See the [Managing user roles doc](/project-setup/managing-roles-and-permissions) for more details. Choose at least one role.
When saving a new user, an invitation email will be sent to the given email address. When saving an existing user, their account will be updated immediately with the changed permissions or roles.

### Send invitation to sign in
When you create a new user and click the **Save** button, the user will receive an email with sign-in instructions.

The invitation expires after 7 days. If the recipient has an active email SPAM filter and Prepr emails end up in the SPAM box, the link expires after 12 hours.

To activate their account, they need to click the **Activate your account** button to go through the Prepr onboarding flow.
This link in the email expires after 7 days.
If the user forgets to accept the invitation and wants to set a password after these 7 days, they need to click the **Reset password** link on the Prepr sign-in page.

### Resend the activation link to sign in
Alternatively, you can resend invitations as long as the user has never signed in.
A user that has not yet signed in can be recognized by the *Invited* label behind his name in the **Users** overview page.
You can either choose to copy the activation link and send it directly through a messaging app or you can resend the invitation email.
- To copy the activation link, hover over the user and click the icon.

- To resend the invitation email, hover over the user and click the icon.

When the user clicks the link you've messaged them or from the invitation email, they are prompted to enter their personal details to complete the onboarding.

Passwords must be at least 8 characters and contain at least one uppercase letter, one lowercase letter and a number. To finalize your account activation, click the **Let's go** button. You are now logged in.

### Delete users
When a user account is no longer needed, you can **Delete** the user if you have the permission to do so, usually as an *Admin* user or the *Owner* of the Prepr account.
This can be done in one of two ways.
- From the **Users** page, hover over the user you want to delete and click the icon to delete this user

- Or click the user that you want to delete to open the the **User** detail page and click the **Delete** button.

### Agency accounts
Agency accounts are user accounts of (web) agencies that take care of the implementation of your website or app.
These accounts do not count toward the number of users you can add within your license. You, therefore, do not pay for agency accounts.
Agency users need to use [2FA](#activate-two-factor-authentication) to sign in.
[Become a Prepr partner](https://prepr.io/agencies/become-a-partner) to register your agency account in Prepr.

### Define user expiration date
You can manage the period a user can have Prepr access. Click the icon and choose the **Users** option. Click the user that you want to manage.

Update the **User expiration date** field on the user detail page. Select the date the account needs to expire or leave it empty to give a user lifetime access.
Prepr sends a notification to the user seven days before the user account expires. One day before the expiration date, the owner of the environments gets a notification email.
After this period, the user access will be revoked. When this user tries to log in, the following message is shown to this user: *Your account has expired. Contact your admin if you need access.*
Any user that has a role (such as the *Owner* or *Admin* role) with the **Users** permission enabled can grant access to the expired user again.

## Managing your account profile
Any Prepr user can manage their own account profile settings by clicking their avatar at the top right of the screen.

### Switch to light/dark mode
You can easily switch between light and dark mode in Prepr by clicking your avatar at the top right of the screen and choosing either the **Light**, **Dark** or **System** option.

- **Light** - the standard theme with a white background.
- **Dark** - uses dimmer background colors and brighter foreground colors to ensure the interface has sufficient contrast and to help reduce eye strain.
- **System** - depends on your system settings (Windows, OS).
### Activate two-factor authentication
Two-factor authentication (2FA) adds a second layer of login security to your Prepr account.
Any user can set up 2FA individually by themselves.
With 2FA enabled, you'll be asked to provide a 6-digit code after you've signed in with your login credentials.
Each code is unique, and can only be used once to log in.
To enable two-factor authentication, perform the following steps:
1. First, download an authenticator app, such as Google Authenticator or Authy from your mobile device.
2. Log in to Prepr and click your avatar on the top right and choose the **Profile** option to open your account profile details.
3. Then, click the *Enable two-factor authentication* toggle.

4. Scan the QR code with your smartphone.
If you can't scan the QR code you can manually enter the 6-digit verification code generated by your authenticator app.
Two-factor authentication is now enabled for your account.
To deactivate the 2FA, simply click the same toggle in your account profile.
5. When 2FA is enabled, the next time you log in with your email and password, you need to provide the special verification code you see in your authenticator app before you can complete the sign-in process.
Each code is unique, and can only be used once to log in.

### Add passkeys
Passkeys allow you to log in to Prepr using your device's security features like a fingerprint, face scan, or PIN, instead of a traditional password.
To add passkeys to your account profile, follow the steps below.
1. Log in to Prepr and click your avatar on the top right and choose the **Profile** option to open your account profile details.

2. Click the **+ Add passkey** link and choose your preferred passkey saved on your device.

3. Change the default name for the passkey if you'd like and click the **Save** button to save the passkey in your profile. You can then add another passkey, if needed.

The next time you access the sign-in screen, you can then choose the **Passkey** button to log in.
### Set language preferences
Go to the *Settings* in your account profile to change your personal language preferences.

When working in a multilingual environment, you can define your preferred language for content items.
For example, if you usually work and translate content items to English, but the environment default is Dutch, you can set your **Language preference** to `en-US` for instance.
For more details, check out the [localization doc](/content-management/localizing-content#working-with-multiple-locales).
In addition to the language preference for content items, you can also choose between English, Dutch and French for the Prepr **Interface language**.
### Manage your personal notifications
Prepr gives you the option to be notified of events in the application.
This way you are always aware of events that are relevant to you.
You can receive notifications via the Prepr application, via browser notifications, and via email.
To activate personal notifications, you can enable notifications by following the steps below.
1. Log in to Prepr and click your avatar on the top right and choose the **Profile** option to open your account profile details.
2. Scroll down and click the **Enable notifications** toggle to choose the triggers and type of notification, via email or in the browser.

There are three types of notifications you can receive.
- Application notifications
You will always see these notifications in the Prepr application. Click your avatar at the top right of the screen to see these notifications.

You can then click the **Mark all as read** link to empty the notification list.
- Email notifications
You can also receive notifications via email. The email always contains an action button, so that you can immediately read the comment, go to the user list or check the webhook edit page.

- Browser notifications
You can receive system notifications via your web browser. To receive browser notifications, you need to allow notifications in your browser and your Operating System. For these, make sure the **Do Not Disturb** option is disabled.

There are several events that you can choose to trigger notifications.
- *I get mentioned in a content item*
You get this notification if someone mentions you when [adding a comment](/content-management/collaboration#commenting) to a content item.
- *a content item gets assigned to me*
You get this notification if someone [assigns any content items](/content-management/collaboration#assignees) to you.
- *a content item is shared with me*
You get this notification if someone [shares any content items](/content-management/managing-content/managing-content-items#share) with you.
- *someone requests access to a content item*
You get this notification as an *Admin* user or the account *Owner* if someone [requests access to a linked content items](/content-management/managing-content/managing-content-items#share).
- *a webhook is failing*
Check out the [webhooks](/development/best-practices/webhooks#response) guide for more details on webhook responses. We recommend developer users enable this notification.
- *a remote source synchronization is disabled*
This notification is useful for developers who manage the integration of content items from remote sources.
- *a new user is signed in via SSO*
A new Prepr user who logs in to the application for the first time using single sign-on will not yet have permissions.
The owner, or another Prepr user with access to user management, must provide him with the correct permissions.
You can enable this notification to check when users log in with SSO for the first time.
Source: https://docs.prepr.io/project-setup/managing-users
---
# Managing roles & permissions
Prepr CMS uses *Role-based access control* to manage user permissions in the application.
This approach makes it easy to manage access for large numbers of users.
You can create and manage user roles if your user role has *Users* enabled in the *Organization level permissions*. For example, if you are an *Admin* user.
To manage user roles, click the environment dropdown at the top left, go to the organization and click the icon to open the environments overview, then go to **User management** and click the **Roles** link to open a list of all roles in your organization like in the image below.

## Default roles
You can find the predefined default roles below which can't be edited or deleted. In addition to these roles, you can create your own.
### Admin
Users with the *Admin* role have access to everything but the billing and plan information.
When added to a specific environment, this user has access to everything within this environment, including general settings, locales, and user management.
When added to the organization settings, the *Admin* user can manage users from all environments in the organization.
### Billing
Users with the *Billing* role have access to the organization’s plan and billing information.
This includes managing payment information, viewing and handling invoices, and overseeing subscription details.
### Developer
Developers have the same content management access as editors, but on top of this they can create and manage webhooks and access tokens.
Developers will also see API details of the content items they view.
### Editor
Users with the *Editor* role can create and manage content items and media.
This role does not allow access to functions at the organization level, but only at the environment level.
### Marketer
The Marketer role is designed for users who manage and optimize audience segmentation within Prepr.
This role has the same permissions as the *Editor* role, allowing users to create and manage content and media.
However, a user with the *Marketer* role also has access to the *Segments* page, enabling them to define and manage audience segments and impact goals.
### Technical contact
Users with the *Technical contact* role get emailed directly in the event of incidents related to failed webhooks and remote sources failing to sync.
They also get an email automatically for announcements on the *Status* page.
## Add or Edit Roles
In addition to the default Prepr roles listed above, you can create and manage your own.
To create a role, in the **User management → Roles** page, click the **Add role** button and give the role a name (required) and description.
On this page, you can specify the permissions of this role.
Each permission gives access to the corresponding pages. For example, when you enable the **Schema** permission, users with this role can access the *Schema* page to create and manage models, components, remote sources and enumerations.

- When you select the **Default user role** option, this role will automatically be added when creating a new user.

### Content permission
By enabling the **Content** permission, you give users access to the *Content* page.
You can further restrict or open content item access at different levels with the granular content management permissions below.
#### Action-based permissions

When the **Content** permission is enabled, you can add the following action-based permissions:
- **Read** - This option is the most strict access and only allows users can only view content items.
- **Commenting** - This option allows users to only add, delete or resolve comments. If only this and **Read** access is enabled, users cannot edit the content item with any other action.
- **Update** - This option allows users to only edit and save a content item.
- **Create** - This option allows users in this role to only create and update content items.
- **Delete** - This option allows users in this role to only delete and update content items.
- **Publish** - This option allows users in this role to only publish and update content items, but not create, delete, unpublish or comment on them.
- **Unpublish** - This option allows users in this role to publish, update and unpublish content items, but not create, delete or comment on them.
- **Bulk update** - This option allows users in this role to perform any action and the corresponding bulk action on content items, except commenting.
#### Specific content access
In addition to granting access to users to perform specific actions on content items, you can also choose the following specific content access options:
- **Items related to user only**

When you enable this option, you allow users to only access content items they created or items explicitly [shared](/content-management/managing-content/managing-content-items#share-a-content-item) with them.
- **Content restrictions** - These options restrict access on content items by their model, locale and workflow stage.

- **Models** - You can choose to grant access to content items related to only specific models. For example, for authors who only work on blog posts.
- **Locales** - You can choose to grant access to specific content item language versions. For example, only German, to a contractor responsible for German translations only.
- **Workflow stage** - You can grant access to content items with specific workflow stages. For example, for a reviewer who only needs to access content items in the *Review* stage.
### Guidelines
When creating or editing roles, please be aware of the following principles:
- Take note of the following behavior when choosing *Content* permission options:
- The **Read** option is always enabled when you enable the *Content* permission.
- You can only enable the **Create**, **Delete** and **Publish** options when **Update** is enabled.
- You can only enable the **Unpublish** option when **Update** and **Publish** is enabled.
- You can only enable the **Bulk update** option when **Update**, **Delete**, **Publish** and **Unpublish** is enabled.
- When you add multiple roles to a user, the most restrictive role will be applicable. For example, consider a custom *Junior editor* role that doesn't allow a user to delete content items. However, the default *Editor* role allows a user to delete content items. So, if you add both the *Junior editor* role and the *Editor* role to a user, they will not be allowed to delete content items.
## Duplicate roles
When you want to use a specific role as a template for new customized roles, you can duplicate the original role.
To duplicate a user role, follow the steps below.
1. Go to the **User management** tab in your organization.
2. Click the **Roles** link at the top right to open the list of roles.
3. Hover over the role you want to duplicate and click the icon to duplicate the role.

## Owner(ship)
In addition to other roles, you can manage the *Owner* role.
An organization can have only one *Owner* and it has administrator rights across all environments.
You can transfer ownership to another user in the organization details of your subscription.
1. Click your account profile icon and choose the **Subscription** option.
2. At the top right, click the **Organization details** link.
3. In the *Contact details* section, click the **Account owner** drop-down menu to change the owner to a different user.

4. Click the **Save** button to transfer ownership. Your own role has now been set to *Admin* and you automatically lose access to the page you're on.
Source: https://docs.prepr.io/project-setup/managing-roles-and-permissions
---
# Setting up single sign-on (SSO)
*This guide shows you how to set up integrations with Microsoft Entra ID, Google Workspace, or any identity provider using OpenID Connect or SAML 2.0 open standards to allow your users to log into Prepr using single sign-on (SSO).*
## Introduction
Prepr offers several ways to log in:
- Using passkeys to allow users to log in using their device pin, facial recognition or fingerprint sign-in options.
- Single sign-on (SSO) is a way to authenticate and log in to an application with just one set of credentials, rather than having to set up multiple usernames and passwords across different platforms. It's a more secure process than submitting an email and password and prevents potentially losing or forgetting log-in credentials since it's stored through another service.
- Submitting an email address with a password.
You can integrate with [*Microsoft Entra ID (Azure)*](#microsoft-entra-id), [*Google Workspace*](#google-workspace), or any identity provider using [*SAML 2.0*](#saml-20) or [*OpenID Connect*](#openid-connect-oidc) open standards if you want to let users sign in from within your company SSO directory controlled by you or your organization.

## Microsoft Entra ID
To allow Prepr users to log in using single sign-on via Microsoft Entra ID, follow the steps below to set up the integration.
## Google Workspace
To allow Prepr users to log in using Google single sign-on, follow the steps below to set up the integration.
## OneLogin
To allow Prepr users to log in using single sign-on with the OneLogin Identity and Access Management solution, follow the steps below to set up the integration.
1. Set up the [OneLogin configuration](https://onelogin.service-now.com/support?id=kb_article\&sys_id=8a1f3d501b392510c12a41d5ec4bcbcc\&kb_category=de885d2187372d10695f0f66cebb351f) with the following details:
- **Entity ID:** `https://auth.prepr.io/saml`
- **Assertion Consumer Service (ACS) URL:** `https://auth.prepr.io/saml/acs`
- Supported field mappings: `first_name`, `last_name`, `email` (The email field mapping is required.)
2. To activate the integration, please contact [Prepr Support](https://prepr.io/support) and supply us with the following configuration data:
- **IdP Entity ID** (unique identifier of the IdP)
- **SSO URL** (a.k.a. “Single Sign-On URL” or “Login URL”)
- **IdP X.509 Certificate**
## OpenID Connect (OIDC)
To allow Prepr users to log in using single sign-on via an OpenID Connect identity provider, follow the steps below to set up the integration.
1. Set up your identity provider with the following with our redirect URI details.
- **Redirect Domain**: \`https://auth.prepr.io
- **Redirect URI**: `https://auth.prepr.io/oidc/callback`
2. To activate the integration with your OpenID Connect identity provider, please contact [Prepr Support](https://prepr.io/support) and supply us with the following configuration data:
- **Client ID**
- **Client Secret**
- **Issuer URL**
- **Discovery endpoints**
We also need the following mapping fields:
- **Unique identifier**
- **Email**
- **First Name** (optional)
- **Last Name** (optional)
## SAML 2.0
To allow Prepr users to log in using single sign-on with an SAML 2.0 identity provider, follow the steps below to set up the integration.
1. Set up the service provider (SP) config with the following details:
- **SP Entity ID:** `https://auth.prepr.io/saml`
- **Assertion Consumer Service (ACS) URL:** `https://auth.prepr.io/saml/acs`
- Supported field mappings: `first_name`, `last_name`, `email` (The email field mapping is required.)
2. To activate the integration with your SAML 2.0 identity provider, please contact [Prepr Support](https://prepr.io/support) and supply us with the following configuration data:
- **IdP Entity ID** (unique identifier of the IdP)
- **SSO URL** (a.k.a. “Single Sign-On URL” or “Login URL”)
- **IdP X.509 Certificate**
Source: https://docs.prepr.io/project-setup/setting-up-sso
---
# Managing your subscription
Quickly scale up or down to support any number of users and environments. Our plans scale with your business requirements, enabling affordable trials and large enterprise implementations. Want to know more?
[Explore our plans and pricing](https://prepr.io/pricing)
[Contact our sales team](https://prepr.io/contact)
## Your current plan
You can check your current plan on the **Subscription** page under your profile icon.
If you would like to have more Users, Environments, and Records than included in your current plan, you can [upgrade your subscription](/project-setup/managing-your-subscription#change-plan) any time.

## User seats in your subscription
One user seat means Prepr access for a user based on their email address.
Users can access Prepr with their email address on two devices simultaneously.
Depending on your plan, you'll see the **Manage** link in the *User seats* section.

Click the **Manage** link to change (add or remove) the number of available user seats.

## Change plan
If you are the account owner, you can upgrade your Community plan by following the below steps:
1. Click the **Upgrade** button.

2. Choose the plan that you want to upgrade to.

3. Choose any additional options for your plan and click the **Get started** button
4. Fill in your payment details and click the **Pay and subscribe** button.
Your upgrade will be applied immediately and the invoice and usage are recalculated in real-time.
To downgrade your current plan, you can contact our sales team from the **Subscription** page. Our sales team will contact the account owner about this request.
## Cancel subscription
We're sorry to see you go! Please note that only the account owner can cancel the subscription. If you're sure you want to cancel, please contact our support team. Just send a message via [support.prepr.io](https://support.prepr.io/) and we will process your cancellation.
## Viewing invoices and payment details
As the Owner, you can view your invoices and payment details. Only the account owner is able to access this page. You can see the amount, status and date of all invoices.
### Download invoices
If you click the title of the Invoice it will open or download a PDF-file. Here you can save it to your PC or print it.
If something is incorrect on an existing invoice, you can contact billing by sending an email to [billing@prepr.io](mailto:billing@prepr.io). Please send the invoice number with your message.
### Change payment details
To change your payment details, including your credit card and VAT numbers, billing email, click your profile icon in the top right corner, then go to **Subscription → Organization details**.
Please note, your changes will only have an effect on future invoices.

## Monitoring data usage
In the organization settings, you can have a closer look at the data usage of your environments.
The usage you see is a sum of the data from all your environments.
We recommend contacting support if you have any questions regarding your data usage.
### Usage metrics
You can easily view your usage data on the subscription page, which helps you track potential additional costs and monitor user API or AI requests.
#### MAU (Monthly active users)
The number of monthly active users across your environments.
#### Bandwidth
The number of Gb of bandwidth used for streaming assets and API's.
#### Storage
The number of Gb of storage of your assets.
#### Transcoding
The number of minutes transcoding. 1 minute of video counts as 1, the minute ratio of audio transcoding is 4:1.
#### API Requests (Informational)
The number of API requests across your environments. This metric is shown for informational purposes only and does not incur additional costs.
#### AI tokens (Informational)
The number of AI tokens used across your environments. This metric is shown for informational purposes only and does not incur additional costs.
### Additional data costs
The usage that exceeds the limits defined in your plan will be charged on your next invoice.
The costs of records, streaming and transcoding are described in your license and SLA.
You'll only see the additional costs for the current month.
For historical usage data, you can check your previous invoices.
### Notify me
Enable the **Notify me** checkbox on the *Subscription* page when you want to receive a notification if costs for your next invoice exceeds a specific value.
You can use this feature to help monitor unexpected peaks in usage.
Source: https://docs.prepr.io/project-setup/managing-your-subscription
---
# Viewing audit log
Audit logs record the occurrence of an event, the time at which it occurred, the responsible user, and the impacted entity. Audit logs are useful for developers when errors occur or if there're inconsistencies in the schema or content.
## Audit entries
Every audit entry includes the user name and the date and time when the event took place.
The list of entities below are tracked and listed in the audit log, if applicable.
|Audit entry object | Audit entry details |
|-----------------------|-------------------------------------------------------------|
| **Content item** | Includes the *Title* of the content item that was created, updated, deleted or published. |
| **Model** | Includes the *Name* of the model that was created, updated or deleted. |
| **Component** | Includes the *Name* of the component that was created, updated or deleted. |
| **Enumeration** | Includes the *Name* of the enumeration that was created, updated or deleted. |
| **Remote source** | Includes the *Name* of the remote source that was created, updated or deleted. |
| **Field** | Includes the *API Field Name* of the field that's created or deleted and the *Name* of the applicable model, component or remote source. |\
| **Webhook** | Includes the *Webhook ID* of the webhook that was created, updated or deleted. |
| **Access token** | Includes the *Token ID* of the app that was created, updated or deleted. |
| **User** | Includes the *User Name* of the user that was created, updated or deleted. |
| **Asset** | Includes the *Asset ID* of the asset that was created, updated or deleted. |
| **Role** | Includes the *Role Name* of the role that was created, updated or deleted. |
| **Segment** | Includes the *Segment Name* of the segment that was created, updated or deleted. |
| **Schema** | Includes the *Name* values of both the source and target environments that were synced for the **Sync schema** process. The **GitHub sync** process indicates if the schema was pulled or pushed. |
## Filtering the audit log
View a selection of specific audit log entries with the following steps:
1. Click the environment dropdown at the top right, choose your organization and click to open the environments overview.
2. Click the *Audit log* link at the top to open a list of all audit entries in all the environments of your organization from today.
3) To filter audit entries by a specific environment, choose the environment name from the **Environment** filter dropdown.
4) To filter audit entries by a specific user, choose the user name from the **User** filter dropdown.
5) To filter audit entries by a specific audit event type like *Created content item*, choose a value from the **Event type** filter dropdown.
If users perform tracked actions while you are viewing the audit log, a button will appear at the top of the list indicating the new actions and giving you the chance to refresh the list.

Source: https://docs.prepr.io/project-setup/audit-log
---
# Set up your organization
## Managing account ownership
When you first sign up for Prepr, your User account is automatically assigned the account owner role. As the account owner, you're the only one with access to subscription information, and the only one who can make updates to plan and payment settings.
### Transfer Account Ownership
We know roles inside your organization can change. Therefore the current owner of the organization can transfer the ownership to another user. If you are the account owner, then you can transfer ownership as follows:
1. Click your account profile icon and choose the **Subscription** option.
2. At the top right, click the **Organization details** link.
3. In the *Contact details* section, click the **Account owner** drop-down menu to change the owner to a different user.

4. Click the **Save** button to transfer ownership. Your own role has now been set to *Admin* and you automatically lose access to the page you're on.
Source: https://docs.prepr.io/project-setup/organization
---
# Setting up your production-ready project
*Welcome to Prepr, a data-driven headless CMS with a built-in personalization engine and optimization features. Learn about the different parts that you need to set up your project with Prepr and the steps to follow to make the best use out of its features for your front-end applications.*
If you can't wait to dive straight into Prepr, follow the [Quick start guide](/quick-start-guide) to get your feet wet.
Learn more about the structure of your Prepr project and then move on to the step-by-step guide to set it up.
Looking for something specific? Check out the detailed resources below.
Source: https://docs.prepr.io/project-setup
---
# Content modeling fundamentals
*This article introduces you to content modeling concepts, the importance of content modeling for developing your front end and explains how to model a robust and scalable content structure for your web app.*
## Key concepts
### Schema
The schema is the content structure for your web app.
It defines the organization and relationships of different types of content, the fields that can be used in the content
and determines how the content is created, stored and accessed.
It consists of [models](#models), [components](#components), [remote sources](#remote-sources) with their associated fields and [enumerations](#enumerations).
A schema in Prepr allows content editors to efficiently add and manage content in the CMS.
Before setting up your schema in Prepr, it's essential to plan and model the content in a suitable design tool.
By doing this, you make sure that the schema aligns with both business requirements and the needs of the front end.
### Models
A content model is a single content type that defines the structure of specific content, like a blog post, product, or event.
It organizes fields, such as text, images, or links defined to store your content.

Check out the [best practices doc](/content-modeling/best-practices#models-vs-components) on choosing models instead of components.
### Components
A component is a predefined set of fields that can be used in multiple models.
You can think of a component as a flexible, reusable template where you define fields once, and then fill them with different content every time you use it in a content item.
Components allow for a consistent content structure across multiple models and are often used for front-end components that are not reused, like hero sections.

This example is a template for SEO information and can be included in different models like an article and page model.
Doing this makes your schema scalable.
If a new SEO field is needed, this can be done by simply adding it to the component. This means you don't have to change any of the models that embed this component.
You can include this component in different types of content, such as pages or articles.
Check out the [best practices doc](/content-modeling/best-practices#models-vs-components) on how to choose a component instead of a model.
### Remote sources
A remote source connects third-party systems, like ecommerce platforms, to Prepr.
It lets you seamlessly access and use external content, such as products or data, within your content items.
You can add a remote source to your schema to define the structure of the content that is maintained in a system outside of Prepr.

Check out the [remote source docs](/content-modeling/setting-up-a-built-in-remote-source) to learn how to set up remote sources.
### Enumerations
An enumeration is a predefined list of values that can be used in models and components.
When you define enumerations, you make sure that predefined, standardized values are used across content.
This reduces the risk of errors or inconsistencies when content editors manage this content.
For example, an enumeration for a button position might have values like `Left`, `Middle` and `Right`.
Checkout the [enumerations doc](/content-modeling/managing-enumerations#add-the-enumeration-to-a-model-or-component) on how to add enumerations to a model or component.
## High level process
Creating a robust schema requires thoughtful planning and collaboration.
The key steps listed below help ensure your schema is efficient, scalable, and aligned with both user and business needs.
By following these steps, you're creating a schema that not only meets immediate needs but also scales and adapts to future challenges.
Now that you understand the basics of schema design, let's take a look at some [examples](/content-modeling/examples) to get a more concrete picture on how to design your schema.
Source: https://docs.prepr.io/content-modeling/fundamentals
---
# Content modeling examples
These examples will help you gain a good sense on how to model your schema. They explain how to set up commonly used UX patterns. Feel free to copy these examples and adjust them according to your needs.
Source: https://docs.prepr.io/content-modeling/examples
---
# Content modeling best practices
*This article introduces you to content modeling best practices and some tips on how to build a scalable schema for developers and ensures a smooth user experience for content editors.*
## Common challenges
### Models vs components
While modeling your content, you may be in doubt about when to choose a model instead of a component and vice versa.
Consider the following points when choosing a model or a component:
- Models focus on what the content is, so usually you start with creating models for your main content, like pages, articles or case studies.
- Models are perfect for reusable content, like categories that can be used in multiple models, like articles and case studies.
- Components often focus on how the content looks, like buttons and sections of a page.
- Choose components for content consistency and presentation.
- Components are great for reusable structures in your front end, like page headers.
In conclusion, use the general guidelines above, but keep an open mind for exceptions.
### Simple vs scalable
When modeling content it's important to create a simple, yet scalable schema.
A scalable schema has a flexible structure that can easily grow and adapt as your needs evolve.
If you make it too simple, it might not be scalable enough and vice versa.
Follow the guidelines below to find a balance between simplicity and scalability in your schema:
- Start small with a minimum viable schema by focusing on essential models, fields, and relationships to support core functionality and immediate goals.
- Avoid “what if” over-engineering. Add fields or relationships only when they bring real value.
- Prioritize editor usability to keep their workflows simple, but leave room for refinement as needs evolve.
By following these guidelines, you'll create an efficient and adaptable schema.
### Nesting models and components
Nesting is structuring models or components in a hierarchy, where one model or component is embedded in another.
Nesting simplifies content management by breaking down content into smaller, reusable parts.

Nesting in content modeling is quite logical for developers because it mirrors the way content is typically structured and consumed in the front end.
However, a completely flat structure makes editing very simple for content editors.
So, it's important to find a middle ground when nesting models and components.
Consider the following points when deciding how deeply to nest models and components:
- You might choose deeply nested schemas to accurately reflect relationships.
But, this can make schemas difficult for editors to manage because they need to drill down multiple levels to make basic changes, which slows workflows and increases the risk of creating errors.

- Flat schemas rely more on field conventions and references instead of nesting. This simplifies editor workflows but requires discipline in naming conventions and content structure.
- Flat schemas reduce the mental load for editors and make content easier to query and scale.
### Page vs Article
A page consists of modular elements so it has to be flexible for custom layouts.
While an article needs to be structured for consistent formatting.
In Prepr, you can enable modular page elements in the page model by using a [*Stack field*](/content-modeling/field-types#stack-field).
To ensure consistent formatting in the article model, you can use the [*Dynamic content field*](/content-modeling/field-types#dynamic-content-field).
The table below indicates the key differences between the two fields and essentially the structure of the *Page* and *Article* models.
|Factor || |
|------|------|---------|
|Flexibility|Highly flexible for custom layouts.|Structured for consistent formatting.|
|Editor Workflows|Editors build pages by stacking pre-configured blocks, such as image galleries and text blocks.|Editors fill in predefined fields like title, body, and tags.|
|Reusability|Focus on reusing layout components across pages.|Focus on reusing content elements like tags and references.|
|Optimization|A/B testing and adaptive content|-|
## Developer considerations
### Generic vs specific models and components
A generic model or component is a flexible model or component, for example, a **Button** component with a styling field like `Button type` with the options, *Primary* and *Secondary*.
Instead, you could opt for separate models or components to make your definition specific, for example, a **Primary button** and a **Secondary button**.
There are no hard and fast rules to choose generic over specific models and components, but note the following considerations when making this decision:
|Factor| Generic|Specific|
|-------|--------|-------|
|Flexibility|Highly flexible with many options.|Purpose-built for specific use cases.|
|Ease of Use|Can overwhelm editors with too many choices.| Simpler for editors, with fewer decisions.|
|Maintenance|Easier to manage one flexible model.|Multiple models may lead to duplication.|
|Scalability|Scales better if options are structured well.|Becomes harder to scale with too many models.|
|Front-end logic|Requires conditional logic in code.| Clearer front end integration for developers.|
### Mapping front-end components to your schema
The schema and front-end often evolve independently, leading to inconsistencies between the schema and front-end components.
Misalignment can make it hard for developers to translate
CMS content into front-end design efficiently.
To resolve this challenge, you can align your front-end components directly to the defined models and components in your schema.
### Maintaining your schema
Consider the following tips to make it easier for you to maintain your schema.
- Create a modeling templates library in a repository like GitHub. This is useful when you work on multiple projects with similar requirements. For example, you could have templates for a *Page*, *Navigation*, *Article*, or *FAQ*.
- Use the *Schema sync* feature in Prepr to keep your development, testing and production environments in sync.
- Use clear, consistent names for models and components.
- Include terms like *Grid*, *List*, *Card*, or *Carousel* to describe their purpose.
- Document your naming rules to keep your team aligned.
We trust that these best practices and tips can help you build a schema that meets requirements, scales and supports a smooth content editing experience.
Check out the [schema fundamentals doc](/content-modeling/fundamentals) for essential concepts and considerations to get started with building your schema.
If you still have questions or doubts, please feel free to [register for the Content modeling workshop](https://prepr.io/resources/content-modeling-webinar).
Source: https://docs.prepr.io/content-modeling/best-practices
---
# Managing models
*This article explains how to create a model in Prepr, including how to manage settings and add fields.*
## Create a model
Use the *Schema Editor* to create different models.
You can create models from scratch, create a model using an existing template or import an model.
When you create a model from scratch, you can choose to create a [*Multi-item model*](/content-modeling/managing-models#multi-item-model) or a [*Single-item model*](/content-modeling/managing-models#single-item-model).
### Multi-item model
In most cases, you'll create multi-item models. These are models for which you allow content editors to create multiple content items.
To create a multi-item model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Then, click the **+ Add model** link and choose the **Multi-item model** option.

3. Name and describe your model as follows and click **Next**.

|General fields | Description|
|--------------------------|--------------|
|*Name* |Choose a unique name for the model|
|*Singular name*| This value is important for making requests with the GraphQL API. When you create a new model, this value is auto-generated as follows: PascalCase version of the model name, stripped of all non-alphanumeric characters. For example, the model name *News article* generates *NewsArticle*.
|*Plural name*| This value is the same as the **Singular name**, except that the auto-generated value is the plural of the *Singular name*, for example, the model name 'News article' generates NewsArticles.|
|*Description*| Fill in a description to help editors manage content by showing the purpose of this model.|
|*Automatically create a shared view for this model*| Selected by default. Creates a shared view to list content items for this model. The view name is set to the plural name of the model.|
6. Choose additional features and click the **Save** button. Check out the [Manage settings](#manage-settings) section for more details.
Now that your multi-item model has been created you can [add the necessary fields](#add-fields-to-a-model) to it.
### Single-item model
In some cases, you want to allow the content editor to only be able to create one content item for a model, for example, to store app configuration settings such as the app name or company info.
To create a single-item model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Then, click the **+ Add model** link and choose the **Single-item model** option.

3. Name and describe your model as follows and click the **Next** button:

|General fields | Description|
|--------------------------|--------------|
|*Name* |Choose a unique name for the model|
|*Singular name*| This value is important for making requests with the GraphQL API. When you create a new model, this value is auto-generated as follows: PascalCase version of the model name, stripped of all non-alphanumeric characters. For example, the model name *News article* generates *NewsArticle*.|
|*Description*| Fill in a description to help editors manage content by showing the purpose of this model. This description is also available as a tooltip in the model and component selection modal (for Stack fields).|
|*Automatically create a shared view for this model*| Selected by default. Creates a shared view to list content items for this model. |
6. Choose additional features and click the **Save** button. Check out [Manage settings](/content-modeling/managing-models#manage-settings) for more details.
Now that your single-item model has been created you can [add the necessary fields](#add-fields-to-a-model) to it.
## Duplicate a model
In some cases you can duplicate a model to save time when you want to create a model that is similar to an existing one.
To duplicate an existing model follow the steps below.
1. Go the **Schema** tab to open the *Schema Editor*.
2. Click the model you want to copy.
3. Click the button at the top of the model.

4. Choose the **Duplicate model** option. The duplicate process is an automatic export and import of the model.
5. Once done, click the **Close** button.
You'll see the duplicated model in the schema with **(Copy)** in the name. You can then rename and edit the duplicated model, as needed. For example, remove fields or add new fields.
## Edit or delete a model
To edit a model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the model that you want to update from the list of models on the left and apply your changes.

To delete a model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the model that you want to delete from the list of models on the left.
3. Click the button at the top of the model.
4. Choose the **Delete** option.

5. Click the **Yes, delete** button to confirm the deletion.
## Manage settings
To change settings for a model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the model from the list of models on the left.
3. At the top of the model, click the **Settings** button to open the setting options for a model.

Let's look at the settings in more detail.
The *General* tab allows you to change the name and description of the model, if needed.
### Settings
Click the *Settings* tab to restrict editors from creating multiple content items or prevent them from deleting content items.

|Settings fields | Description|
|--------------------------|--------------|
|*Allow multiple content items* |Disable this toggle for single-item models, for example, for app configuration.|
|*Allow creation from content item list*| Enabled by default. Disable this option to hide this model from the *Add item* selection list.|
|*Disable deletion of content items*| Enable this toggle when you don't want content editors to delete these content items, for example, an *App configuration* content item.|
### Appearance
Click the *Appearance* tab to upload a preview *Image* or to set a *Tag* for the model.

These settings help editors identify their content items more easily in stack or reference fields.
The preview image helps the editor visualize the content, while the tag puts content items of this model into a logical group.
### Features
Click the *Features* tab to configure the options that a content editor can see in the list of actions for the corresponding content item.

|Features | Description|
|------------------|--------|
|*Visual Editing*| This option is enabled by default. When you disable this setting, the icon will not be visible at the top of the content item page to [show Visual Editing](/content-management/managing-content/managing-content-items#visual-editing).
|*Workflow*| This option is enabled by default. When you disable this setting, the *Workflow stage* and [*Assignee*](/content-management/collaboration#assignees) won't show at the top of the content item page. Disable workflow for items that don't require collaboration with other content editors such as menu items or categories.|
|*Scheduling*| This option is enabled by default and allows content items to be scheduled for publishing or archiving. When you disable this setting, content editors won't see any [scheduling options](/content-management/managing-content/managing-content-items#schedule-a-content-item) in the *Publish* drop-down action list at the top of the content item page.|
|*Content check*| This option is enabled by default and makes the **Content check** icon available when editing a content item. When a content editor clicks the icon, the feature automatically reviews the content item for validation issues and SEO optimization. For more details, check out the [Reviewing content guide](/content-management/reviewing-content).|
|*Engagement insights*|When you enable this setting, the content editor can see and analyze metrics on content items by clicking the **Show metrics** option in the action list from the icon.|
|*Commenting*|When you enable this setting, the content editor can [see and make comments](/content-management/collaboration#commenting) for the purpose of reviewing content items at the top of the content item page.|
|*Versioning*| When you enable this setting, Prepr keeps track of all the changes in a content item. It allows a content editor to revert to a previous version when needed and to view the revision history. For more details, check out the [content item versions doc](/content-management/managing-content/managing-content-items#manage-versions).|
### Preview
Click the *Preview* tab to add URLs where content editors can preview content items.
For more details on these settings, check out the [Preview content items doc](/project-setup/setting-up-previews-and-visual-editing).

## Add fields to a model
To add fields to your model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the model that you want to update from the list of models on the left.
3. Drag and drop the desired field type, for example, *Text*, from the list on the right into your model.

For a complete list and all the specs, check out [Prepr field types](/content-modeling/field-types).
Let's look at some basic settings that are common across all field types.
4. Using *Text* as an example, fill the *General* settings as follows:
|General fields | Description|
|--------------------------|--------------|
|*Name*| The field label shown in the content editing interface.|
|*ID*| The value of this field is automatically generated. The technical ID of this field that is used, for example, to retrieve content through the API.|
For more details on the other *Text* settings, check out the [*Text field type*](/content-modeling/field-types#text-field).
## Add sections
Use sections to determine the display of the fields in your content item. It allows you to organize the view of a content item for your content editors and is useful for grouping topics. For example, separate the metadata from the content such as a group of SEO fields versus article content.
To add a section, follow these steps:

1. Click the **Schema** tab and click to open the model from the list of models on the left.
2. Click the **+ Section** button. Fill in a name and description (optional) to save the section.
3. Drag the section above the fields that you want to group.
You can also update how a section is displayed by clicking the icon in the section to edit the section settings.
Click the **Appearance** tab to update some display settings:
|Appearance settings | Description|
|------------------|--------|
|*Help text*| An instruction to help content editors. Make instructions clearer by making text **bold**, *italic*, or ***bolditalic***. Use the following standard Markdown syntax: \* or \_ (italic) \*\* or \_\_ (bold) \*\*\* or \_\_\_ (bolditalic). |
|*Collapse section in content item*| Enable this value to collapse the section, by default when a content editor opens the content item.|
|*Conditional visibility* -> **Show this section based on another field's value**| Enable this value if you want to make the section visible in the content item depending on another field value. You can add the following types of conditions depending on the field type: **All** - For any field type you can check if another field in the model or component is not empty or empty. **Boolean** - You can choose to match one of the boolean values. **List** - You can choose to match one or more values in the list.**Text** - You can choose to match by regex pattern, for example: Show the *Email fields* section if a text field is an email address.|
## Defining field width
You can define the width of a field display in the corresponding content item.
Defining field width allows you to how fields are displayed next to each other instead of using up the 100% width of the screen.
The more scannable display makes it easier for content editors to add or update content.
To define a field width, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click the model from the list of models on the left.
3. Hover over field and click the **100%** indicator.
4. Choose the % width for this field and select the **Start on new line** option, if applicable.

## View system fields
System fields are system-generated and read-only fields that provide information about the content. For example, the *CreatedOn* field is the date when the content was created. Enable the **Show system fields** toggle to see the list of system fields that are available on a model.
Use the *API ID* on the right of the field to query the values for these fields in your API request. Click the icon to see more information about the field. Check out the [GraphQL API reference docs](/graphql-api/schema-system-fields) for more details.

## Organize models into folders
When you have dozens of models, folders make it easy to find related models. For example, when you have multiple models which are used as sections of a page.
Hover over the models and click the icon.
Enter a name for the new folder and choose the models that you want to include in the new folder.
Click the **Add folder** button.

To add or remove models from an existing folder,
hover over the folder and click the icon.
Choose the **Edit** option and select or deselect the models that you want to add or remove.
You can also delete the folder by selecting the **Delete** option.
The folder structure will be removed and the included models will return to the alphabetical list of models.
## Search models
When you type any keyword in the search bar at the top of the **Schema** page, Prepr performs a fuzzy search on the folder name, model name, component name, enumeration name or remote source name.
## Export and import a model
Use the export and import if you only need a couple of models copied from one environment to another. For example, export a model from your staging environment and import the model into your production environment.
To sync a schema with models, components, enumerations and remote sources from another environment then follow the process detailed in the [Sync schema doc](/development/working-with-cicd/syncing-a-schema) instead.
To share the same models across multiple environments, for example when an organization has different brands, but needs content in separate environments, you can create a shared schema as detailed in the [Shared schemas doc](/project-setup/architecture-scenarios/shared-schema).
To export a model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click a model from the list of models on the left.
3. Click the button at the top of the model.
4. Click **Export model** to download a JSON file of the model.

To import a model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Then, click the **+ Add model** button.
3. Click **Or import a model**.
4. Choose the JSON file of the model that you want to import. When you import a model, the settings are also copied across.

Source: https://docs.prepr.io/content-modeling/managing-models
---
# Field types
*This article contains a comprehensive list of all the field types available in Prepr.*
## List of field types
|Fields | Model| Component|
|--------------------------|---------|-------|
|[Text](/content-modeling/field-types#text-field) For single and multi line text entry. |✓|✓|
|[Assets](/content-modeling/field-types#assets-field) To add one or more assets (image, video, audio, file).|✓|✓|
|[Stack](/content-modeling/field-types#stack-field) To create a stack of components and/or models often used for component-based pages. This field is where you enable the A/B testing and personalization features. |✓|✓|
|[Content reference](/content-modeling/field-types#content-reference-field) To create a link to another model.|✓|✓|
|[Component](/content-modeling/field-types#component-field) To add a custom **Component** to a model or another component. |✓|✓|
|[Dynamic content](/content-modeling/field-types#dynamic-content-field) An advanced editor for structured elements.|✓|✓|
|[Slug](/content-modeling/field-types#slug-field) To add part of a URL that describes the path to a specific content item. |✓|✗|
|[Boolean](/content-modeling/field-types#boolean-field) To select one of two possible values (Yes or no, 1 or 0, true or false). |✓|✓|
|[List](/content-modeling/field-types#list-field) To select an option from a predefined list.|✓|✓|
|[Number](/content-modeling/field-types#number-field) To add an integer or float to your content item. |✓|✓|
|[Remote content](/content-modeling/field-types#remote-content-field) To reference content in a remote source.|✓|✓|
|[Form](/content-modeling/field-types#form-field) To include forms from an external source like HubSpot or Typeform.|✓|✓|
|[Date and time](/content-modeling/field-types#date-and-time-field) To add a date and time to your content item.|✓|✓|
|[Tags](/content-modeling/field-types#tags-field) To add a tag entry. |✓|✓|
|[Location](/content-modeling/field-types#location-field) To select a location on a map or use coordinates. |✓|✓|
|[Social](/content-modeling/field-types#social-field) To add a social media post to your content item.|✓|✓|
|[Color](/content-modeling/field-types#color-field) To select a color or use a hex color code. |✓|✓|
|[Help text](/content-modeling/field-types#help-text-field) To provide and highlight additional instructions to editors. |✓|✓|
## Common settings
You can customize how fields appear and behave when editing content items.
Here are some field settings that are common across almost all field types.
When you create a new field, you need to enter the **Display name**. The value you enter is the field label in the content item.
The **ID** value is automatically generated from the **Display name** value you enter and is useful to retrieve content through the API.

To change the **Display name**, simply click the field in the model, component, enumeration or remote source to open up the settings.
However, the **ID** value does not change automatically.
Click the **Appearance** tab to define **Help text**, **Conditional visibility** and the **Field display**.

### Defining the help text
There are several options to display help text for a field.
Enter a clear instruction to help content editors and use **bold**, *italic*, or ***bolditalic*** to emphasize key words.
You can use some standard Markdown syntax:
- `*` or `_` for italic
- `**` or `__` for bold
- `***` or `___` for bold and italic.
|Help text option | Description|
|------------------|--------|
| **Show help text above field** | Choose this option to display the help text just above the field|
| **Show help text in tooltip next to field name** | Choose this option to display a tooltip next to the field name, a content editor can see when they hover over the icon|
| **Show help text inside the field**| Choose this option to display the help text directly in the field box as a placeholder value.|
### Setting up conditional visibility
Enable the **Show field based on another field's value** if you want to make this field visible in the content item depending on the value of another field.
You can add the following types of conditions depending on the field type:
|Field type | Available conditions|
|------------------|--------|
| **All** | For any field type you can check if another field in the model or component is not empty or empty.|
| **Boolean** | You can choose to match one of the boolean values. |
| **List** | You can choose to match one or more values in the list, for example: Show *Model year* when the *Product type* is `Car`.|
| **Text** | You can choose to match by regex pattern, for example: Show *Subject* field if field is an email address.|
### Defining the field display
The **Field display** options let you choose to keep a field editable, or make it read-only or only visible to developers.
|Display option | Description|
|------------------|--------|
|**Default** | The default UI setting. This option allows the field to be edited by any user in the UI.|
|**Read only**| Disables the field in the UI and it can only be edited through the API.|
|**Hidden for non developers**|The field is only visible in the UI for users with developer permissions.|
### Validation rules
Click the **Validation** tab to see the validation rules for the field.

Enable **This field is required** if this is a mandatory field.
The validation only triggers when a content item is set to the *Required field validation stage* defined in the [environment settings](/project-setup/setting-up-environments#manage-environment-settings), usually the **Done** workflow stage.
For more details, check out the [Workflow stages doc](/content-management/collaboration).
## Text field
The text field allows content editors to add single and multi-line text elements to your content item.
Click the **Settings** tab to fill in the following settings:

|Settings | Description|
|--------------------------|--------------|
|**Single line** |Allows only one text line to be entered|
|**Text area**| Multiple text lines without layout options|
|**HTML editor**|Multiple text lines with layout options, like heading, bold, italic, underlined, list, dynamic and external links, table, and alignment.|
|**Initial value**| You can enter a fixed value or choose a field to prefill this value automatically when a content editor creates the content item. The value can be overwritten manually. For a dynamic initial value, simply click the corresponding API name from the list of fields in the info box. The list of fields includes the system fields `id`, `user.full_name`, `user.first_name`, `user.last_name` and any other *Text* field, *Content reference* field, like `categories.title`, *List* field or a *Remote source* field, like `shopify.id`.|
If you choose the *HTML editor* text field, then you can also enable/disable **Heading** options in the settings:

Enable the **Heading** option to allow headings and choose which of the headings (1 - 6) will be available to content editors.
When you create a new Text field and enter a fixed **Initial value** (instead of choosing a dynamic value from the list) and content items already exist for the model, a confirmation modal appears.
Click the **Yes, apply,** button in the modal to automatically update all linked content items with this value.

For details on **Appearance** options, check out [the basic field settings](#common-settings).
Click the **Validation tab** to set up validation rules:

|Validation | Description|
|--------------------------|--------------|
|**This field is required** |Prevents saving a content item if this field is empty|
|**This field must be unique** |Prevents saving a content item if this field value already exists for this field in another content item. This validation can only be enabled for a *Single line* type for a text field.|
|**Allow scripts**| Make it possible to copy-paste or add (HTML-)code in a content item|
|**Limit character count**| Specifies a maximum allowed number of characters. This option is useful, for example in titles when your website or app accepts a limited number of characters|
|**Match a specific pattern**|Only accepts specified regular expression (regex). Validates that the value of a field matches a specific pattern defined by a regular expression.
For your convenience, you can choose from a number of common predefined regex rules:
- **Email**: An email consists of a user name, followed by ‘@’ followed by a domain name. Characters allowed in a user name are alphanumeric characters (a-z, 0-9), ‘\_’, ‘.’, ‘-’.
- **URL**: A valid URL requires a protocol prefix (http, https) and a top-level domain, like
- `domain.com`
- `www.domain.com`
- `http://domain.com`
- `https://domain.com`
- `https://www.domain.com`
- **Date (European)**: Dates in the format ‘DD/MM/YYYY’. Single-digit months and days may or may not have a leading zero. You can use / and - and . as digit-dividers:
- d.m.yyyy
- dd.mm.yyyy
- d/m/yyyy
- dd/mm/yyyy
- d-m-yyyy
- dd-mm-yyyy
- **Date (US)**: Dates in the format:
- m.d.yyyy
- mm.dd.yyyy
- m/d/yyyy
- mm/dd/yyyy
- m-d-yyyy
- mm-dd-yyyy
- **12h Time**: Accepts time values in the HH:MM:SS AM/PM format. Allowed hours are from 01 to 12, columns are required, while the use of seconds is optional. The input must contain AM/PM notation in either lower- or upper-case:
- hh:mm AM
- hh:mm:ss AM
- hh:mm PM
- hh:mm:ss PM
- **24h Time**: Accepts time values with format HH:MM:SS. Allowed hours are from 01 to 24. The input cannot contain AM/PM notation:
- hh:mm
- hh:mm:ss
Please note that predefined regex rules don't work on HTML editor text fields. To make a regex work on a HTML editor, you need to add a custom regex, like `^(?:<(.*)>car(?:<\/\g{1}))$` (instead of `^car`).
### Item title and SEO fields
By default, the first text element in your model or component is used as a title. This label is used to display the item in lists.
You can manually change the title to another text element by clicking the icon and choosing the action for that field.
You can also mark a field as an **SEO title** or **Meta description**.

The text field labels in the model overwrite the text field labels in a component used in the same content item.
For example: If the title in a *Page* is set as the `SEO title` and the page content item includes a *Header* component with a text field that is also set as an `SEO title`, then the model text field will be used as the SEO title for the page content item.
For the **AI** setup check out the [Text AI assistant guide](/ai-text-assistant).
### A/B testing
Click the **A/B testing** tab to enable or disable the A/B testing. When it's enabled, you allow content editors to add A/B testing variants to a text field.

## Assets field
Prepr supports multiple asset types: images, files, videos, and audio files. For more details on how to work with assets, check out the [Work with assets guide](/content-management/managing-assets).
When adding a new assets field to a model/component, you can choose between a Multiple-asset field or a Single-asset field depending on the number of assets you want to add. The chosen type can be changed later in the field configuration.

From the **General** tab, select which **Asset type** is allowed in this field, such as image, video, audio, or file.
Here, you can also change the **Field type** you've chosen previously, from multi-asset to single-asset, and vice versa.

### Assets field settings
Click the **Settings** tab to choose the following settings for an *Assets* field:

| | Description|
|--------------------------|--------------|
|**Set as multi-asset field**| Enabled by default. This option allows content editors to add more than one asset to their content item for this field. Disable this option if you want to restrict the field to one asset only, such as the cover image for a blog post.|
|**Allow alignment**|Enable this option to allow the content editor to left-, center- or right-align the asset.|
|**Allow caption**|Enable this option to allow the content editor to provide a caption for the asset.|
|**Set image focal point**|Enabled by default. This option allows content editors to set a focal point in the image.|
|**Focal point is required**|This setting is visible when you enable **Set image focal point**. Enable this option to make it mandatory for the content editor to set an image focal point.|
|**Set image presets**| Enable this option for content editors to crop images. When enabled, you can define multiple presets for cropping. The *Image presets* are image size (aspect ratio) templates you can define for different device types or use cases. For example, you may want to define a preset for displaying images on mobile or using them in a banner. Note that crop values are not based on image resolution.|
|**Cropping is required**| This setting is visible when you enable **Set image presets**. Enable this option to make it mandatory for the content editor to crop the image.|
For more details on how to resize assets in the API, check out the [GraphQL field types doc](/graphql-api/schema-field-types#assets)
and the [REST API docs](/mutation-api/assets-resizing).
For more details on how to use the image options, check out Edit and configure assets [Edit and configure assets](/content-management/managing-assets/editing-and-configuring-assets).
Click the **Validation** tab to indicate if this field is required.

For the multi-asset field type, you can also enable the Limit input option and set a minimum and a maximum number of assets allowed.
## Stack field
The **Stack** field allows content editors to 'stack' multiple components and content items within a single field.
It also allows them to personalize elements in a stack.
Check out the [Personalization](/personalization/setting-up-personalization) guide for more details.
The stack field is often used to structure the elements for web pages.
When you add a stack field to a model or component, you can choose to create a single stack. For example: if a page content item can only have one header.
The chosen type can be changed later in the field configuration.

Define the general options as follows:
|General options | Description|
|--------------------------|--------------|
|**Allowed models and components** | Select all the models and components that the content editor can include in this stack. |
|**Allow new items to be created**| For each model, disable this option if the content editor should only reference existing items while compiling the stack. |
|**Allow items from all environments**| For each selected model, enable this option to allow content editors to include content items for this model from other environments within the organization. This allows the content to be shared across an organization. For more details on shared content, check out the [Environments doc](/project-setup/setting-up-environments#shared-content).|
Click the **Settings** tab to define the settings below.

| Settings | Description|
|--------------------------|--------------|
|**Show in filters**|You can hide this field from the content item filter menus by disabling this setting.|
|**Allow multiple content references and components**|Disable this option to limit the content editor to adding a single referenced item or a single component in the stack of the model or component. |
|**Initial value**|You can set an initial value to a list of existing content items and components to help content editors create a content item more quickly.|
Click the **Validation** tab to enable the **Limit input**. Enable this option to set a minimum and maximum allowed number of referenced content items and components.

Click the **A/B testing** tab to enable or disable the A/B testing. If you disable it, this feature will not be available to content editors to add A/B testing groups to this stack field.

Click the **Adaptive content** tab to enable or disable the *Adaptive content* feature. If you disable it, content editors will not be able to personalize elements in this stack field.

Check out the [Example Page pattern](/content-modeling/examples/page) doc for details on how to use the stack field in your model or component.
## Content reference field
The content reference field allows content editors to make a reference link from one content item to another.
When you add a content reference field to a model or component, you can choose to create a single content reference.
The chosen type can be changed later in the field configuration.
From the **General** tab, select the following options:

Select the models and complete the general options as follows:
|General options | Description|
|--------------------------|--------------|
|**Allowed models** |Select all the models that the content editor can use within the content reference field.|
|**Allow new items to be created**| For each model, disable this option if the content editor should only reference existing items.|
Click the **Settings** tab to view the following settings:

| Settings | Description|
|--------------------------|--------------|
|**Show in filters**|You can hide this field from the content item filter menus by disabling this setting.|
|**Allow multiple content references**|Disable this option to limit the content editor to adding a single referenced item to the model or component. |
|**Initial value**|You can set an initial value to an existing content item that prefills the value when a content editor creates a content item.|
Click the **Appearance** tab to set the type that determines how the field is displayed on the content item.

Choose to show a link field as a *Modal with search*, an *Autosuggest dropdown*, or as *Checkboxes*.
Click the **Validation** tab to enable the **Limit input**. Enable this option to set a minimum and maximum allowed number of referenced content items.

## Component field
The **Component** field allows content editors to include the predefined fields from a previously created [component](/content-modeling/managing-components).
From the **General** tab, choose the component that you want this model to use.

Go to the **Appearance** tab, to choose the display options.

|Appearance options | Description|
|--------------------------|--------------|
|**Display fields as a group** |Enabled by default to display the component fields in a group with a title separated from other fields in the content item. When this toggle is disabled, component fields are displayed like any other content item field. |
## Dynamic content field
The dynamic content field allows content editors to combine various elements. Those elements can include text, headings, lists and even components.
The dynamic content field is commonly used to create articles with a variety of content and different styling for each element.
This field offers different elements, with variable options that you can manage in the model:
Click the **Content types** tab to enable the following elements:


|Settings | Description|
|--------------------------|--------------|
|**Heading** |Enable to allow headings and choose heading 1 - 6.|
|**Paragraph**| **Allow layout** - Enable this option and choose from eight formatting options. **Allow scripts** - Make it possible to copy-paste or add (HTML-)code in a content item.
|**Assets**|Enable to allow assets, then choose types of assets and formatting options.|
|**Social**|Enable to allow one or more of the major social platforms.|
|**Other**|Enable to include location.|
|**Remote sources and forms**|Enable to allow editors to include remote content or forms in content items. This option is available when at least one [remote source](/content-modeling/setting-up-a-built-in-remote-source) or form integration like [HubSpot](/integrations/hubspot) or [Typeform](/integrations/typeform) is set up in Prepr.|
|**Components**|Enable to choose previously created [components](/content-modeling/managing-components). A list of components are made available to choose from when this option is enabled.|
## Slug field
A slug is part of a URL that describes the path to the specific content item. It is the part that comes after your domain name in the URL. You can only add one slug field per model.
Fill in the **Settings** tab as follows:

|Settings | Description|
|--------------------------|--------------|
|**Slug template**| This value is used to auto-generate a slug value when a content editor creates a new content item or clicks the regenerate icon. Construct the template by clicking the corresponding API name from the list of fields in the info box. The list of fields includes the system fields `id`, `locale`, `country`, `lang`. The list of fields also include any other *Text* field, for example: `name` and `excerpt`, *Content reference* field, for example: `categories.id` and `categories.slug`, *Remote content* field, for example `e_commerce_product.name`, or *List* field as defined in the model. |
|**Automatically generate unique slugs**| When a new content item is created with a slug value that already exists, Prepr auto-generates a unique slug in the format **provided slug template** + **'-'** + **#**, where **#** is an incremented number, for example, news-article-1 or news-article-2. When this setting is disabled, no unique slug will be generated, but an error will occur to indicate a duplicate content item.|
|**Remove trailing slash**| This option ensures that slugs never end with a forward slash. If a trailing slash is added manually, it will be automatically removed when the slug field loses focus. This helps maintain clean, consistent URLs across your content.|
|**Make slug prefix read-only**|When enabled, editors can only modify the part of the slug that comes after the last slash. In other words, the first part of the slug becomes read-only. When this option is disabled, editors can modify the whole slug value.|
Click the **Validation tab** to set up validation rules:

|Validation | Description|
|--------------------------|--------------|
|**This field is required** |Prevents saving a content item if this field is empty.|
|**Limit character count**| Specifies a maximum allowed number of characters. This option is useful, for example to make the slug more readable for web app visitors.|
|**Match a specific pattern**|Only accepts specified regular expression (regex). Validates that the value of a field matches a specific pattern defined by a regular expression.
For an example on how to use the slug field, check out the [preview URL setup guide](/project-setup/setting-up-previews-and-visual-editing#set-up-preview-urls).
## Boolean field
This field allows content editors to choose one of two possible values.
You can define these values in the **Appearance** tab.

|Appearance | Description|
|--------------------------|--------------|
|**True condition label** |Specify a label for a true value|
|**False condition label** |Specify a label for a false value|
In the **Settings**, you can choose the **Default value**. When the content item is created, this value will be set automatically and a content editor can overwrite it, if needed.

If content items already exist for the model, a confirmation modal appears when you save the field.

Click the **Yes, apply,** button in the modal to automatically update all linked content items with the **Default value**.
## List field
Adding list field to a model or component allows content editors to make a choice out of the given options.
From the **General** tab, choose an [enumeration](/content-modeling/managing-enumerations) with the key-value pairs you need for this list.

Click the **Settings** tab to hide this field from the content item filter menus by disabling the **Show in filters** setting.

You can also set a **Default value**. The value entered here is automatically prefilled on the content item, but content editors can overwrite this value manually.
When you enter a **Default value** and content items already exist for the model, a confirmation modal appears.
Click the **Yes, apply** button to automatically update all linked content items with this value.
## Number field
The number field can be useful when you want to add numerical data to your content item. For example year of birth, stock number, or product prize.
The Integer and Float fields can hold 32-bit safe values.
From the **General** tab, you can choose the **Number type**: **Integer** or **Float**.

Click the **Settings** tab to set an **Initial value**. The value entered here is automatically prefilled on the content item, but content editors can overwrite this value manually.

When you enter an **Initial value** and content items already exist for the model, a confirmation modal appears.
Click the **Yes, apply,** button in the modal to automatically update all linked content items with this value.
Click the **Validation** tab to enable validation rules.
|Validation | Description|
|--------------------------|--------------|
|**This field is required** |Prevents saving a content item if this field is empty.|
|**This field must be unique** |Prevents saving a content item if this field value already exists for this field in another content item.|
|**Limit input**| Enable this option to set a minimum and maximum allowed value.|
## Remote content field
The **Remote content** field allows editors to use content maintained in an external system in content items.
On the **General** tab, name the field and choose the desired remote source. For more details, see the available [integrations](/integrations).

## Form field
The **Form** field allows editors to include forms from an external system like *HubSpot* or *Typeform* in content items.
On the **General** tab, name the field and choose the platform where the form comes from.

For other options, check out the [basic field settings](#common-settings).
### Using plain text or HTML encoded variables
By default, you can pre-fill the title and ID of the content item, but you can also choose to add other fields to your embed structure. You can only use API IDs of fields that you have added to your model. Each text field will generate a plain text variable as well as an encoded variable.
When using embed codes, it can be necessary to use encoded URLs, so that specific characters are encoded to an HTML structure (f.e.: spaces are encoded into %20 characters). To use an encoded variable, click the **.encoded** field to add.

### Building the embed code
Once you have added the embed HTML to your model, you will see this field in all content items of this model. All variables (API IDs) are replaced in real-time with the data you enter in your content item.
### Plain text embed

### Encoded embed

### Using the embed code
To use the embed code on your front-end or a third-party website, simply click ' Copy code' to save the embed code to your clipboard.
## Date and time field
The field Date and time allows content editors to add dates and times to your content items.
From the **General** tab, choose a **Type** as follows:
- **Date** - A date-only field
- **Date range** - A start date and end date
- **Business hours** - A day and time picker to enter AM and PM openings hours.

Click the **Settings** tab and fill the settings as follows:
|Settings | Description|
|--------------------------|--------------|
|**Allow time selection (hh:mm)**| Enable this option to add a time to your date or date range. |
|**Allow extra dates**|Enable this option to use multiple date or date ranges.|

## Tags field
This field allows content editors to add tags to your content item.
In the **Appearance** settings, choose one of the follow **Type** values:
- **Free tag entry**
- **Restrict entry to tag group**
- For the **Restrict entry to tag group** option, choose a predefined tag group and choose either **Checkboxes / Radiobuttons** or **Autosuggest**

If you have the SEO score option selected in a model, tag suggestions will be visible as the **Free tag entry** field. For example:

## Location field
The field Location allows content editors to add Google Maps geo-points (coordinates or an address) to your content item.
See the [Basic field settings](#common-settings) on how to add a *Location* field to a model. Check out [Adding elements to a content field](/content-management/managing-content/creating-rich-content) for an example on how location content is added.
## Social field
Prepr supports several social embeddings. You can select one of them for each social field. In a content item, editors can copy and paste a social URL. Prepr will generate a preview of the social post.
From the **General** tab, select the type as follows:

## Color field
The color field allows content editors to choose a color. For example, this is useful when you want to manage your front-end styling in Prepr.
Click the **Settings** tab to set an initial value. This means that the value is prefilled on the content item automatically. This value can be overwritten manually.

## Help text field
Unlike other fields, the help text field is not an entry in a content item.
This field is a definition of how to display any additional guidance instructions in a content item.

When you add a help text field, simply set a *Title* that appears in bold at the top of an information box.
Add your instructions or help text in the *Description* value. You can use standard Markdown to highlight key instructions.

Go to the **Appearance** tab to set the **Background style**.
You can customize the background color by choosing *Default* (light gray) *Info* (blue) or *Warning* (yellow) to highlight appropriate guidance.
Check out the [common settings](#setting-up-conditional-visibility) for details on how to set up the **Conditional visibility**.
The instructions will appear in a highlighted box in the position you add the field in the model or component.
In the example below, the field was added as the first field in the *SEO* component.

Source: https://docs.prepr.io/content-modeling/field-types
---
# Defining the Asset model
*This article explains how to set up the asset model in Prepr to add fields to assets and to enable localization.*
## Introduction
The Asset model is a model that's part of the Prepr *Schema* and [*Shared schema*](/project-setup/architecture-scenarios/shared-schema) by default and cannot be deleted. The Asset model allows you to define additional fields and to enable localization for all assets.
## Asset fields
The fields listed below are the core fields always available in an asset. These fields are also used for internal purposes, for example: in thumbnails, media browser, for Alt text, etc.
- *Internal name* - This field is required and is used to enter the *Title* of the asset.
- *Author* - This optional field can be used to enter the name of the photographer or the visual designer of the asset.
- *Description* - This optional field is used by editors to describe the asset.
These core fields cannot be removed or redefined in the *Asset* model.

## Add fields to the Asset model
Sometimes the front end app needs more information about the asset beyond the *Title*, *Description* and *Author*. For example: If there is a requirement to display the copyright information with the image. In this case you can add more fields to the assets with the following steps:
1. From the **Schema** tab, click the *Asset* model.
2. Drag and drop one of the field types from the list on the right into the model. The following field types are available when adding fields to the assets:
- [Text](/content-modeling/field-types#text-field) - Use this type to allow content editors to enter details like copyright information. The following settings are **not** available on the Text fields in the Asset model:
- Unique constraint validation
- Allowing scripts
- AI features
- **Links** option (in the HTML editor)
- [Boolean](/content-modeling/field-types#boolean-field) - Use this field type to set a True/False indicator in the assets.
- [List](/content-modeling/field-types#list-field) - Use the list field type for content editors to select values from an enumeration.
- [Number](/content-modeling/field-types#number-field) - Use this field type to store number-related info in the assets.

## Enable localization in assets
In some cases, content editors need to enter asset information in different languages or define different info depending on the locale. For example: To include cultural context and nuances. To allow content editors to do this, you can enable [localization](/content-management/localizing-content) in the *Asset* model with the following steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the *Asset* model.
3. At the top of the model, click the **Settings** button to open the setting options.
4. Toggle the **Enable creation of language variants** switch to allow users to add language variants for each custom field added to the Asset model.

Source: https://docs.prepr.io/content-modeling/defining-the-asset-model
---
# Managing Components
*This article explains how to manage a component in Prepr. A component is a predefined set of fields that can be used in models.*
## Create a component
To create a component, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add component** button.
3. Name the component as follows [Or import a component](/content-modeling/managing-components#export-and-import-a-component):
|General fields | Description|
|--------------------------|--------------|
|**Display name** |Choose a unique name for the component.|
|**Type name**| The value of this field is automatically generated. This field is important for the GraphQL API to connect your front-end to Prepr. When you create a new component, this name is generated as follows: PascalCase version of the **Display name**, stripped of all non-alphanumeric characters. For example, the component name **"SEO tags"** generates **SEOTags**.|
|**Icon**| Choose an icon image that visually represents your component. This is used for the dynamic content editor and other parts of the content item editor interface.|

After creating a component, you can include a component in a model. The following model fields allow you to use your component in a model:
- [Component field](/content-modeling/field-types#component-field): This field type allows the model to include the component.
- [Dynamic content field](/content-modeling/field-types#dynamic-content-field): This field type allows the component fields to be available in the **Dynamic Content Editor**. Check out the [Dynamic Content field type](/content-modeling/field-types#dynamic-content-field) for more details.
- [Stack field](/content-modeling/field-types#stack-field): This field type allows the model to include multiple components and models.
See an example empty component used as a placeholder, below:

## Duplicate a component
In some cases you can duplicate a component to save time when you want to create a component that is similar to an existing one.
To duplicate an existing component follow the steps below.
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the component you want to copy.
3. Click the button at the top of the component.

4. Click the **Duplicate component** option. The duplicate process is an automatic export and import of the component.
5. Once done, click the **Close** button.
You'll see the duplicated component in the schema with **(Copy)** in the name. You can then rename and edit the duplicated component, as needed. For example, remove fields or add new fields.
## Manage settings
To change settings for a component, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the component from the list of components on the left.
3. At the top of the components, click the **Settings** button to open the setting options for a component.
The following settings are available:
### General
The *General* tab allows you to edit the names and description for the component.

|General fields | Description|
|--------------------------|--------------|
|*Display name* |A unique name for the component.|
|*Type name*| This value is important for making requests with the GraphQL API. When you create a new component, this value is auto-generated as follows: PascalCase version of the display name, stripped of all non-alphanumeric characters. For example, the model name *News article* generates *NewsArticle*.
|*Description*| A description to help editors manage content by showing the purpose of this model.|
### Appearance
Click the *Appearance* tab to upload a preview *Image* or to set a *Tag* for the component.

These settings help editors identify the component more easily in stack or reference fields.
The preview image helps the editor visualize the content, while the tag puts the component into a logical group.
## Add fields to a component
To add fields to your component, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click on the component from the list of models on the left.
3. Drag and drop the desired field type, for example, **Text**,

For a complete list and all the specs, check out all [Prepr field types](/content-modeling/field-types).
Let's look at some basic settings that are common across all field types.
4. Using **Text** as an example, name it as follows:
|General fields | Description|
|--------------------------|--------------|
|**Display name**| The field label shown in the content editing interface.|
|**ID**| The value of this field is automatically generated. The technical ID of this field that is used, for example, to retrieve content through the API.|
For more details on the other **Text** settings, check out the [Text field type](/content-modeling/field-types#text-field).
5. Click the **Validation** tab to set up validation rules for the field.
Enable **This field is required** if this is a mandatory field. The validation only triggers when a content item is set to the **Done** stage. For more details, check out the [Workflow stages doc](/content-management/collaboration).
For more details on the other **Text** validation options, check out the [Text field type](/content-modeling/field-types#text-field).
## Set a component title
By default, the title of a component in a *Stack* or *Dynamic content* field is set to the first *Text* field in the component or to the value of the *Number*, *Content reference* or *List* field when the component only has one of these fields.
At times, you may want the component title to be the title of a referenced content item. For example, when the embedded component title should be the headline of an article.
Also, a component might just have a *List* or *Number* field, and in those cases, you might want the component title to be the chosen list item value or the number entered.
For example, a number of the item that defines its order in a list.
To set a *Text*, *Content reference*, *Number* or *List* field value as the component title, click the icon in the field and choose the **Set as title** option.

## Delete a component
To delete a component, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the component from the list of components on the left.
3. Click the button at the top of the component.
4. Choose the **Delete** option.

## Defining field width
You can define the width of a field display in the corresponding content item.
Defining field width allows you to how fields are displayed next to each other instead of using up the 100% width of the screen.
The more scannable display makes it easier for content editors to add or update content.
To define a field width, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click on the component from the list of components on the left.
3. Hover over field and click the **100%** indicator.
4. Choose the % width for this field and select the **Start on new line** option, if applicable.

## Create a nested component
Nested components can be added to models directly, to a *Dynamic content* field or to a *Stack* field.
This feature allows you to add an individual (*child*) component into another (*parent*) component, creating nested levels of content. You can reuse individual components as many times as needed, even if they're already used within nested components. For example, you can use a call-to-action component in both a page header component and a product collection component.
To create a nested component, follow these steps:
1. [Create individual components](/content-modeling/managing-components#create-a-component) which represent different elements in your page layout, such as header, image and text, call-to-action, product or article collections, etc.
2. Determine a *parent component* – the one you’ll be using as a container for other components (*child components*). Click to open its configuration.
3. From the parent component page, add a new component field and select the needed child component. Save your choice.

Now that you have created the nested component, you can [add it to a model directly](/content-modeling/field-types#component-field) or [place it within a Stack field](/content-modeling/field-types#stack-field).
## Organize components into folders
When you have dozens of components, folders make it easy to find related components. For example, when you have multiple components which are used as sections of a page.
Hover over the components and click the icon.
Enter a name for the new folder and choose the components that you want to include in the new folder.
Click the **Add folder** button.

To add or remove components from an existing folder, hover over the folder and click the icon.
Choose the **Edit** option and select or deselect the components that you want to add or remove.
You can also delete the folder by selecting the **Delete** option.
The folder structure will be removed and the included components will return to the alphabetical list of components.
## Search components
When you type any keyword in the search bar at the top of the **Schema** page, Prepr performs a fuzzy search on the folder name, model name, component name, enumeration name or remote source name.
## Export and import a component
Use the export and import if you only need a couple of components copied from one environment to another. For example, export a component from your staging environment and import the component into your production environment.
To sync a schema with models, components, enumerations and remote sources from another environment then follow the process detailed in the [Sync schema doc](/development/working-with-cicd/syncing-a-schema) instead.
To share the same components across multiple environments, for example when an organization has different brands, but needs content in separate environments, you can create a shared schema as detailed in the [Shared schemas doc](/project-setup/architecture-scenarios/shared-schema).
To export a component, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click the component that you want to export from the list of components on the left.
3. Click the button at the top of the component.
4. Click **Export component** to download a JSON file of the component.

To import a component, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add component** button.
3. Click **Or import a component**.
4. Choose the JSON file of the component that you want to import.

Source: https://docs.prepr.io/content-modeling/managing-components
---
# Managing enumerations
*This article explains how to create and use an enumeration in Prepr. An enumeration is a predefined list of values that can be used in models and components.*
## Create an enumeration
To create an enumeration, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add enumeration** button.
3. Fill in a unique **Display name** value for the enumeration.
4) Add each of the list values and click the **Save** button. The *Name* values are visible to content editors.

5. Instead of adding each value manually, you can switch to the JSON editor to paste a JSON with all your values. Then click the **Save** button.

Once your enumeration is created, it's ready for you to use in a model or component.
## Edit an enumeration
You can edit the values in an enumeration by going to the button at the top of the enumeration and clicking the **Edit enumeration values** option.

You can then either edit the values manually or switch to the JSON editor to paste a JSON with your updated values.

Don't forget to click the **Save button** to save your changes.
## Hide or delete an enumeration value
In the case that an enumeration value becomes redundant, you may want to prevent content editors from using it in content items.
When this value is already used in content items, you can hide this value instead of deleting it.
To hide an enumeration value, simply go to the value, click the icon and choose the **Hide** option.

When a value is not used in content items, you can delete the enumeration value, by choosing the **Delete** option.
You are notified when the value you're trying to delete is in use.
When an enumeration value is hidden or deleted, editors will not be able to choose this value for any new content items.
When viewing an enumeration you can easily see which values are hidden by the icon.
## Duplicate an enumeration
In some cases you can duplicate an enumeration to save time when you want to create an enumeration that is similar to an existing one.
To duplicate an existing enumeration follow the steps below.
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click the enumeration you want to copy.
3. Click the button at the top of the enumeration.

4. Click the **Duplicate enumeration** option. The duplicate process is an automatic export and import of the enumeration.
5. Once done, click the **Close** button.
You'll see the duplicated enumeration in the schema with **(Copy)** in the name.
You can then rename and edit the duplicated enumeration, as needed.
## Add the enumeration to a model or component
Go to the model or component where you want to include the enumeration and follow the steps below.
1. Add a *List* field to the model or component. Check out the [model](/content-modeling/managing-models#add-fields-to-a-model) and [component](/content-modeling/managing-components#add-fields-to-a-component) docs for more details.
2. From the **Select enumeration** dropdown, choose your enumeration.

And that's it. You've successfully included your enumeration in a model or component.
## Organize enumerations into folders
When you have dozens of enumerations, folders make it easy to find related enumerations. For example, when you have multiple enumerations which are used in the same components.
Hover over the enumerations and click the icon.
Enter a name for the new folder and select the enumerations that you want to include in the new folder.
Click the **Add folder** button.

To add or remove enumerations from an existing folder, simply drag them out or into the folder.
You can also delete the folder by choosing the **Delete** option.
The folder structure will be removed and the included enumerations will return to the alphabetical list of enumerations.
## Search models
When you type any keyword in the search bar at the top of the **Schema** page, Prepr performs a fuzzy search on the folder name, model name, component name, enumeration name or remote source name.
## Export and import an enumeration
Use the export and import if you only need a couple of enumerations copied from one environment to another. For example, export an enumeration from your production environment and import it into your development environment.
To sync a schema with models, components, enumerations and remote sources from another environment then follow the process detailed in the [Sync schema doc](/development/working-with-cicd/syncing-a-schema) instead.
To share the same enumerations across multiple environments, for example when an organization has different brands, but needs content in separate environments, you can create a shared schema as detailed in the [Shared schemas doc](/project-setup/architecture-scenarios/shared-schema).
To export an enumeration, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click the enumeration that you want to export from the list of enumerations on the left.
3. Click the button at the top of the enumeration.
4. Click the **Export enumeration** option to download a JSON file of the enumeration.

When the export is successful, you'll find the JSON file in the location you selected.
To import an enumeration, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add enumeration** button.
3. Click the **Import enumeration** link.
4. Choose the JSON file of the enumeration that you want to import.

When the import is successful, you'll see the detailed enumeration in your schema.
Source: https://docs.prepr.io/content-modeling/managing-enumerations
---
# Setting up a built-in remote source
*This article explains how to use a known 3rd party system as a remote source in Prepr.*
## Introduction
Prepr allows you to use any external system as a remote source to reference images, videos, or other content in your content items.
You can choose to connect to one of the predefined sources: *Shopify*, *BigCommerce*, *Commercetools*, *Commerce Layer*, *Propeller*, or add a custom one.
To connect to a prebuilt remote source, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Go to the **Remote sources** section and click the **Add source** link.
3. In the opened dialog window, choose the prebuilt source you want to connect to.

4. Depending on the source you choose, provide the necessary connection details.
### Shopify
To connect to Shopify, you need to provide the following details.
|Field | Description |
|----------------------- |-------------------------------------------------------------------------------------------------------------------------------------------- |
| **Display name** | A unique name to identify this source in Prepr. You can change the automatically generated value. |
| **Type name** | A name used for accessing this source through the API. The value matches the prefilled *Name*. |
| **Shopify Storefront Access token** | The authentication credential that allows Prepr to make requests to the Shopify's Storefront API. |
| **Shopify Storefront Domain** | The web address (URL) that Prepr can use to access the online store.|
Check out the [Shopify integration guide](/integrations/shopify) for more details.
### BigCommerce
To connect to BigCommerce, you need to provide the API headers for the **Access token** and the **Domain**.
Check out the [BigCommerce integration guide](/integrations/bigcommerce) for more details.
### Commercetools
To connect to Commercetools, you need to provide the following details.
|Field | Description |
|----------------------- |-------------------------------------------------------------------------------------------------------------------------------------------- |
| **Display name** | A unique name to identify this source in Prepr. |
| **Type name** | A name used for accessing this source through the API. The value is generated automatically and matches the *Name* you specified. |
| **Project key** | The identifier of your [Project](https://docs.commercetools.com/api/projects/project). Copy the 'project\_key' value from [your API Client information](https://docs.commercetools.com/getting-started/create-api-client#view-your-api-client-configuration). |
| **Client ID** | Your [client credential](https://docs.commercetools.com/api/projects/api-clients) that is used to obtain an access token. Copy the 'client\_id' value from [your API Client information](https://docs.commercetools.com/getting-started/create-api-client#view-your-api-client-configuration). |
| **Secret** | Your [client credential](https://docs.commercetools.com/api/projects/api-clients) that is used to obtain an access token. Copy the 'secret' value from [your API Client information](https://docs.commercetools.com/getting-started/create-api-client#view-your-api-client-configuration). |
| **Scope** | The [scope](https://docs.commercetools.com/api/scopes) defines the endpoints to which a client has access and the permissions. Copy the 'scope' value from [your API Client information](https://docs.commercetools.com/getting-started/create-api-client#view-your-api-client-configuration). |
| **API URL** | The [endpoint URL](https://docs.commercetools.com/getting-started/make-first-api-call#url-and-endpoints) that is used to make the API calls. Copy the 'API URL' value from [your API Client information](https://docs.commercetools.com/getting-started/create-api-client#view-your-api-client-configuration). |
| **Authorization URL** | The [authorization URL](https://docs.commercetools.com/getting-started/make-first-api-call#auth-url-and-parameters) that is used to obtain an access token. Copy the 'Auth URL' value from [your API Client information](https://docs.commercetools.com/getting-started/create-api-client#view-your-api-client-configuration). |
| **Display locale** | Locale defines content language. Select a preferred language to use in the API requests to Commercetools. |
Check out the [Commercetools integration guide](/integrations/commercetools) for more details.
### Commerce Layer
To connect to Commerce Layer, you need to provide the following details.
| Field | Description |
|------------------ |-------------------------------------------------------------------------------------------------------------------------------------- |
| **Display name** | A unique name to identify this source in Prepr. |
| **Type name** | A name used for accessing this source through the API. The value is generated automatically and matches the *Name* you specified. |
| **Base URL** | The endpoint URL that is used to make the API calls. Copy the value from the [Commerce Layer API specification](https://docs.commercelayer.io/core/api-specification#base-endpoint). |
| **Client ID** | Your [client credential](https://docs.commercelayer.io/core/authentication/client-credentials) that is used to obtain an access token. Copy the 'client\_id' value from [your Integration API credentials](https://docs.commercelayer.io/core/api-credentials#integration). |
| **Secret** | Your [client credential](https://docs.commercelayer.io/core/authentication/client-credentials) that is used to obtain an access token. Copy the 'client\_secret' value from [your Integration API credentials](https://docs.commercelayer.io/core/api-credentials#integration). |
Check out the [Commerce Layer integration guide](/integrations/commerce-layer) for more details.
### Propeller
To connect to Propeller, you need the **Propeller API key** to connect.
Check out the [Propeller integration guide](/integrations/commerce-layer) for more details.
Once you've connected to one of the prebuilt remote sources, Prepr will automatically create the necessary fields based on that integration.
## What's next?
Once you've set up the remote source, proceed with the following steps:
1. Add the remote source to your model using the [Remote content field](/content-modeling/field-types#remote-content-field).
2. Editors can then add remote content to content items.
3. [Retrieve data using the API](/graphql-api/schema-field-types#content-integration) from your front end.
Source: https://docs.prepr.io/content-modeling/setting-up-a-built-in-remote-source
---
# Creating a custom remote source
*Follow this guide to add 3rd party content to your web application using Prepr CMS.*
## Introduction
When creating content items for your web application, you may want to reference content stored in an external system such as another CMS, legacy system, or ecommerce platform.
With Prepr, you can easily use any 3rd party system as a remote source by following the guide below.
## Setting up a custom remote source
To set up a custom remote source, Prepr CMS needs a connection to the external system through an API endpoint.
For remote sources not configured in Prepr, you need to first create the custom API endpoint, [validate it](#validate-the-custom-remote-source), and set up the connection details in Prepr to allow editors to add content from this external system to their content items.
## Next steps
Once the remote source is set up and editors have [included the remote content](/content-management/managing-content/creating-rich-content#adding-remote-content) in their content items, you can [retrieve the remote content](/graphql-api/schema-field-types#remote-content) for those content items using the GraphQL API.
Source: https://docs.prepr.io/content-modeling/creating-a-custom-remote-source
---
# Content modeling
*Explore the resources below to get started with content modeling and learn how to set up a well-defined schema in Prepr CMS.*
Before diving into Prepr, learn the basics about content modeling and how to model content using some typical examples like a *Blog*, *Page* and *Personalization*.
Dive into Prepr and learn how to set up a schema by managing models, components, setting up remote sources and other more advanced features.
Source: https://docs.prepr.io/content-modeling
---
# Next.js + Prepr CMS
Need info on Prepr for a Next.js app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up pages exactly the way your marketers want them.
## Acme Lease demo website
## Next.js Quick start guide
## Next.js Complete guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Next.js blog examples
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to set up A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs
---
# Nuxt + Prepr CMS
Need info on Prepr for a Nuxt app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up personalized pages exactly the way your marketers want them.
## Nuxt Quick start guide
## Nuxt Complete guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs
---
# Laravel + Prepr CMS
Need info on Prepr for a Laravel app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up personalized pages exactly the way your marketers want them.
## Laravel Quick start guide
## Laravel Complete guide
## Laravel SDKs
## Other resources
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel
---
# React + Prepr CMS
This overview page gives you the resources you need to connect your React project to Prepr.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/react
---
# Vue.js + Prepr CMS
Need info on Prepr for a Vue app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## Vue.js Quick start guide
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/vuejs
---
# Angular + Prepr CMS
Need info on Prepr for an Angular app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## Angular Quick start guide
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/angular
---
# Node.js + Prepr CMS
This is the official Node.js + Prepr guide. It’ll explain to you how to quickly set up a Prepr client in your Node application and use it to fetch data from the Prepr CMS.
## Getting started
In this guide, you’ll follow an example that’ll teach you how to set up a minimalistic Node application making use of Express (a Node framework), set up a Prepr client and serve the data received using it to the client through a get request.
## Prerequisites
This guide assumes that you’ve:
- An active Prepr account.
- Set-up an environment on Prepr CMS, preferably using the demo data provided by Prepr. This can be done by clicking on Load demo data when setting up the environment.
## Setting up a Node application
To begin, create a directory for your application and enter it using the following commands:
```bash copy
mkdir prepr-node
cd prepr-node
```
Initialize your project using `npm init -y`.
Install necessary packages:
```bash copy
npm install express nodemon dotenv
```
Update your `package.json` file to have `app.js` as your main entry point and include the following watch script under scripts:
```js copy
"watch": "nodemon app",
```
Now, create a file named app.js using touch app.js and add the following code into it:
```js copy
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get("/", (req, res) => {
res.send("Hello world");
});
app.listen(port, () => {
console.log(`App listening on port: ${port}`);
});
```
Run the application using following command:
```bash copy
npm run watch
```
Now, upon visiting `http://localhost:3000/`, you should see the following output:

## Creating a Prepr client
Close the server you started in the above step and add the Prepr’s Javascript SDK to your app by using the following command:
```bash copy
npm install @preprio/nodejs-sdk
```
Create a `services` directory in the root of your project and in here, create a file named `prepr.js` using the following commands:
```bash copy
mkdir services
cd services
touch prepr.js
```
You’ll use this file to create and configure your Prepr client with the help of `createPreprClient` provided by the SDK. The client will be used to make API requests to endpoints provided by the Prepr CMS in your Node application.
Add the following code to this file:
```js copy
require("dotenv").config();
const { createPreprClient } = require("@preprio/nodejs-sdk");
const prepr = createPreprClient({
token: process.env.PREPR_ACCESS_TOKEN, // You can find one in your Prepr environment
baseUrl: "https://graphql.prepr.io/graphql",
userId: null, // Optional, used for AB testing implmentations
});
module.exports = { prepr };
```
Prepr recommends using environment variables to store your sensitive information like access token. To add environment variables, create a `.env` file in the root directory of your project and add the variable like this:
```
PREPR_ACCESS_TOKEN=
```
## Writing GraphQL query
Now, let’s write the GraphQL query that you’ll use in this example.
Create a `queries` directory in the root of your project and in here, create a file named `preprQueries.js` using the following commands from the root of your project:
```bash copy
mkdir queries
cd queries
touch preprQueries.js
```
Add the following code to this file:
```js copy
const GetPosts = `
query GetPostsQuery {
posts: Posts {
items {
_id
_slug
title
}
}
}`;
module.exports = { GetPosts };
```
If you’re using the same preloaded demo data in your CMS environment as mentioned above in the Prerequisites section, you should have 5 posts in there. This query, GetPosts, will be used to fetch the post titles.
In case, you’re not using the demo data provided by Prepr then your query might be different. To test your query, you can make use of [Explorer](https://studio.apollographql.com/sandbox/explorer) provided under Apollo Studio’s sandbox mode. There you can easily create a query with the help of the documentation pane and check the results in real time. All you’ll need is your unique graphql endpoint, All the necessary information about the endpoint can be found [here](/graphql-api/authorization).
## Fetching and sending data as response
Open `app.js` in the root of your project and replace its content with the following code:
```js copy
const express = require("express");
const { prepr } = require("./services/prepr");
const { GetPosts } = require("./queries/preprQueries");
const app = express();
const port = process.env.PORT || 3000;
const posts = [];
const preprData = async () => {
const postsData = await prepr.graphqlQuery(GetPosts).fetch();
posts.push(...postsData.data.posts.items);
};
preprData();
// Query for the root path.
app.get("/", (req, res) => {
res.send({ data: posts });
});
// Listen to application port.
app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});
```
What you’re doing here is importing the Prepr client and the GraphQL query you created in above sections into your `app.js`. Then you run the GraphQL query, `GetPosts`, using the Prepr client and push the data received into the posts array initialized right above it. Finally, you send the posts as response to the get request on the root path of your server.
Run the application again using the `watch` script.
Now, upon making a get request against your server’s root path or visiting it in the browser, you should see a similar response as shown in the image below:

## Learn more
For additional information on using @preprio/nodejs-sdk, checkout its [documentation](https://github.com/preprio/nodejs-sdk).
Source: https://docs.prepr.io/connecting-a-front-end-framework/nodejs
---
# PHP + Prepr CMS
Need info on Prepr for a PHP app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## PHP Quick start guide
## PHP SDKs
## Other resources
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/php
---
# Astro + Prepr CMS
Need info on Prepr for an Astro app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## Astro Quick start guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/astro
---
# Svelte + Prepr CMS
Need info on Prepr for a Svelte app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up personalized pages exactly the way your marketers want them.
## Svelte Quick start guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/svelte
---
# Why you don't need an SDK with Prepr CMS
When implementing a headless CMS, you might assume you need to use a dedicated SDK to include Prepr CMS in your front end.
Since Prepr CMS includes a GraphQL API for content delivery, using *Apollo Client* or a similar GraphQL client to fetch content eliminates the need for an SDK.
## Apollo Client already handles everything you need
[Apollo Client](https://www.apollographql.com/docs/react) is a powerful GraphQL client that simplifies data fetching and management. It provides:
- Querying so you can fetch content directly with GraphQL.
- Built-in caching mechanisms to improve performance and reduce API calls.
- State management to normalize data and manage it efficiently.
Since GraphQL APIs are self-documenting and strongly typed, *Apollo Client* allows you to interact directly with the CMS without requiring additional SDKs.
## Apollo Client is scalable
*Apollo Client* is scalable because it's optimized for edge deployment and serverless applications as follows:
- It comes with normalized cache that reduces redundant requests and optimizes data retrieval, improving performance in distributed environments.
- It minimizes network requests by batching queries and deduplicating them, which is beneficial for scalable applications.
- It works well with edge computing platforms by reducing latency and improving data fetching performance.
- It integrates seamlessly with serverless GraphQL APIs (like Apollo Server running on AWS Lambda).
## Easier debugging and tooling
Apollo Client integrates seamlessly with developer tools like *Apollo DevTools*, making it easier to inspect queries, analyze cache, and debug issues—capabilities that may not be as robust when using a CMS-specific SDK.
## Next steps
Check out [one of the framework quick start guides](/connecting-a-front-end-framework) to get you started in a few minutes to implement your front end using [Apollo Client](https://www.apollographql.com/docs/react).
To make the most of all Prepr features including A/B testing and personalization, check out the [Next.js complete guide](/connecting-a-front-end-framework/nextjs/next-complete-guide).
Source: https://docs.prepr.io/connecting-a-front-end-framework/using-an-sdk
---
# Connecting a front-end framework
The flexibility of a data-driven headless CMS allows you to connect your favorite front-end framework, deliver content and optimize and personalize the visitor journey.
Source: https://docs.prepr.io/connecting-a-front-end-framework
---
# Developer fundamentals guide
*In this guide, you'll find everything you need to know to develop with Prepr CMS. At a high level, it introduces you to essential concepts and guides you toward more information about these concepts.*
## Understanding the architecture
It's important to understand the architecture of a headless CMS for you to make the best decisions for your approach, technical designs, testing and deployment.
Here are some key concepts to consider before you get started.
- **Decoupled backend and front end**: Unlike a traditional CMS, a headless CMS provides content via APIs. Content management and presentation are completely separate. This makes it easier for you to build flexible, multi-platform experiences.
- **API-first approach**: Familiarize yourself with the APIs that the CMS exposes. Prepr CMS exposes a GraphQL API for fetching content and a RESTful API for managing content.
- **Microservices mindset**: A headless CMS often fits into a modular web architecture (MACH). In this setup each component, such as search or authentication, is a microservice that needs to be integrated seamlessly.
## Developing with a headless CMS
The steps listed below give you a high level outline of the activities you can expect when developing with a headless CMS.
We trust that we have guided you to implement a successful web app with Prepr CMS.
Are you missing some developer-related resources or specific info, [please let us know](https://forms.gle/B4SezB1rhzEqeGLe8).
Source: https://docs.prepr.io/development/fundamentals
---
# Best practices for developers
*Check out our offering of best practices to develop streamlined and robust code for a headless CMS.*
Source: https://docs.prepr.io/development/best-practices
---
# Working with CI/CD
Discover advanced Prepr CMS features designed to support your CI/CD processes.
Source: https://docs.prepr.io/development/working-with-cicd
---
# Developing with Prepr CMS
*Discover everything you need to know to develop with Prepr CMS, including resources for connecting front-end frameworks, best practices, managing CI/CD pipelines and integration guides.*
Source: https://docs.prepr.io/development
---
# Managing content
*Learn about managing content items, improving SEO and readability and creating rich content in Prepr CMS.*
Source: https://docs.prepr.io/content-management/managing-content
---
# Managing assets
*Learn how to upload, manage and store your assets centrally in Prepr CMS to create photo galleries, video blogs, or even start a live-streaming event in your web application.*
Source: https://docs.prepr.io/content-management/managing-assets
---
# Reviewing content
This guide provides you details on content management features you can use to help you review your content items.
These help you identify and fix common issues like missing fields, broken links, and nonoptimal SEO.
You can review content using the following key features:
- *Content check*
- *Needs attention* view
## Content check
When creating a new content item or editing an existing content item, you can trigger the *Content check* when it's [enabled for the corresponding model](/ai-text-assistant#checking-seo-values).

The content check helps you review the content for a number of issues:
- Empty required fields and values exceeding a character limit length
- Broken links
- [Nonoptimal SEO values](/content-management/managing-content/optimizing-content-for-seo)
## Needs attention view
You can easily find content items with content quality issues by clicking the **Needs attention** view in the sidebar of the **Content** page.
Click to open a content item directly from this list and fix the issue.

The list of content items in the **Needs attention** view include the following types of issues:
- Content items with broken internal or external links.
- Content items that could not be published because the workflow stage is not set to *Done*.
Source: https://docs.prepr.io/content-management/reviewing-content
---
# Measuring content impact
*Easily set up and measure the impact of your content items directly in Prepr.*
## Introduction
Content item metrics help you understand the impact of your content.
See how many people you reach, how deeply they engage, and whether your content contributes to marketing goals and conversions.
Use these insights to identify what works, improve underperforming content, and focus your efforts on content that delivers results.
## Setting up content item metrics
You can view content metrics on content items when Prepr tracking is implemented in the front end and the feature is enabled for certain content items.
The metrics are visible in the content item list in the *VIEWS* column.
The metric value is the total number of views for that content item ever.
When you hover over the graph, the tooltip shows you the number of views in the last 7 days.

If there are no values and graphs visible, follow the steps below to set up the content item metrics.
Once done, you'll see content metrics in the content item list and in each relevant content item detail page.
## Viewing content item metrics
You can view the content item metrics in detail by either clicking the **View all metrics** link from the tooltip in the content item list or directly in the content item page.

You can filter all the metrics by a custom **Date range**.
By default, the date range is set to the last 7 days (excluding today).
You can change this to any date range in the past with a maximum of 30 days.

The following metrics are available: Reach, Engagement, Events and Performance
### Reach
You can determine the reach of content items from the number of times a content item is viewed during the chosen date range.

You can switch between the number of *Views* or number of *Unique visitors*.
Together, they tell you how often an item was viewed and how many individual visitors those views came from.
The values at the top of the graph are the average number and trend in the chosen date range.
While the graph gives you a visual of the trend over the period with exact values per day.
#### Views
Views help you understand the size of the audience your content attracts.
The number of views is the number of times this content item has been viewed by visitors.
#### Unique visitors
The total number of unique visitors gives you more context into the reach over the chosen date range.
| Views | Unique Visitors | Reach |
| ----- | --------------- | ------------------------------------------------------------------- |
| Low | Low | Low discovery - The content struggles with search engine visibility, internal linking, and repeat engagement.
| Low | High | Single-visit traffic - Traffic lands once to answer a quick query, then exits without exploring further. |
| High | Low | High repeat views - A core group of dedicated users relies on this page as a reference hub, returning repeatedly. |
| High | High | Broad + high retention - The page successfully attracts a wide audience and drives consistent repeat visits. |
### Engagement
Engagement tells you about what happened after visitors viewed this content item.

Average time spent (in seconds) and average scroll depth help you understand whether people actually engaged with the content during the chosen date range.
#### Avg. time spent
A higher average time spent can indicate that visitors are reading or exploring the content more thoroughly.
Interpret this metric in context: shorter content may naturally require less time than a detailed article or guide.
#### Avg. scroll depth
The average scroll depth is how far visitors scroll through the content, represented as a percentage.
A higher scroll depth suggests that visitors are reaching more of the content.
If scroll depth is consistently low, consider strengthening the introduction, improving readability, or moving important information higher on the page.
### Events
You can also see the number of events sent by your front end for this content item during the chosen date range.

For example, a click on a call to action, a video being played, or a form being opened.
You can see the number of all events that visitors perform or number of times a specific event was triggered.
The numbers at the top of the graph is the total number of events and the trend over the chosen date range.
While the graph gives you a visual of the trend and specific number of events on certain days.
### Performance
Performance shows whether this content item contributes to the outcomes you care about.

#### Assisted goals
A goal is a result you've defined for your website, such as a quote request, demo booking, or signup.
Assisted goals show how many times a content item was viewed in a session that also resulted in one of those goals in the chosen date range.
#### Conversion rate
The conversion rate shows you the rate of the assisted goal versus the number of views.
In other words, what percentage of total views resulted in completed goals.
#### Impact score
Impact score measures how much this content item contributed overall to achieving goals during a visitor journey.
View the *Impact score* to get a quick understanding of how well this content performed in the chosen date range.
A *High* impact score means this content item is in the top 20% of all content contributing to goals during the chosen date range.
For more insight into content metrics in the context of real-life visitor journeys, check out our [Content metrics blog](https://prepr.io/blog/introducing-content-item-metrics-in-prepr).
Source: https://docs.prepr.io/content-management/measuring-content-impact
---
# Localizing content
## Working with localization
Multi-channel content publishing also means managing content in different languages. Prepr offers unlimited locales for your content. Effortlessly localize your content into any language. Assign local versions to different users with a dedicated workflow and scheduling for their location.
### Adding a locale
Locale values in Prepr appear as standard language codes (ISO i18n), for example, `en-US` or `de-DE`.
You can add locales to a single environment and at [organization level](/graphql-api/localization).
Every environment can have its own default locale and locale list.
Follow the steps below to add a new locale to a specific environment.
1. Click the icon and choose the **Locales** option. Here, you can select one or more locales for multi-language content items and set a default.
2. Choose the locale value from the drop down to add a new locale to the environment and click the **Save** button.

### Working with multiple locales
If your Prepr environment is set up with multiple locales, you can see a summary of the locale versions for a content item from the content item list by going to the **Content** page.
To see the workflow summary for each locale, hover over the user icon of a content item.

To see the status of each locale for a content item, hover over the status info like in the example below.

You can also filter your content items by language in the **Content** page.
Click the *Language* filter at the top of the content item list with the default locale selected. The content item title in the content item list is the title in the default locale.

#### Default locale
When you log in to Prepr, the content items are listed in the default locale.
When you create a content item, this item also starts in the default locale.
The default locale is set for the [environment](/project-setup/setting-up-environments#create-an-environment), but you can override this value by setting a default locale in your profile.
To set your own default locale, click your user avatar at the top of the screen and choose the *Profile* option and update your **Language preference** in the *Settings* section. Click the **Save** button to save your user preferences.

### Create a language variant
If you want to create another language variant, go to your main content item and choose the language from the drop-down menu in the side bar.
In the drop-down menu, you can see the workflow stage of each locale entry, for example, *Not created*, *To do*, *In progress* or *Done*.
A modal then opens from which you can click one of the following options:
- *Translate automatically using AI* - Choose the source language if more than one is available and then choose the tone of voice or click the **Skip** button to start automatic translation. Prepr translates the text and creates the content item entry in your chosen language. You can then update this content item entry to refine the translated text.

- *Copy content and translate manually* - Choose the source language that you want to copy. Prepr creates a copy of the content item with the exact same text. You can then translate the text manually.
- *Start from scratch*. - Prepr creates a content item with empty fields. You can then fill the content item from scratch.
### Retranslate a language variant
In some cases you might want to completely replace the text in a language variant with a new translation.
For example, when the source language variant has a lot of changes since the first translation.
Instead of deleting the language variant, and redoing the translation from the source language, you can simply use the **Retranslate** option.
1. From the language variant you want to retranslate, click the icon and choose the **Retranslate** option.

2. Follow the same process as you would when creating a new language variant.
3. When you choose to *Translate automatically using AI*, you are given the option to either keep the existing tone of voice of the variant or to change it.
Once done, save or publish your retranslated language variant.
If versioning is enabled for the content item, you can restore the previous translation by viewing the [version history](/content-management/managing-content/managing-content-items#manage-versions).
## Working with assets in multi-language content items
Do you want to use and describe one asset in different content items? That is facilitated by Prepr: it is possible to add a caption to the asset(s) per content item. In this way, you can have the caption in one content item deviate from the caption in the other content item. This is how you can describe the asset very precisely for your specific content item.
### Manage captions in multi-language content items
Do you work with content items in multiple languages? The caption of an asset is language-dependent. This means you can have the caption in one language differ from the description in the other language.
To manage the caption in an asset per language, follow the steps below.
1. Go to the **Content** tab and select the right content item. Hover over the thumbnail of the image you want to edit and click the icon. Choose the **Edit caption**.
2. Enter a caption the matching language and click the **Save** button.
3. Save the content item and switch to another locale.
4. Repeat the above steps to edit the caption for the same image in a different language.
5. Save the content item to apply the new caption.

Source: https://docs.prepr.io/content-management/localizing-content
---
# Collaborating with coworkers
## Manage workflows
Use workflows to assign content items to team members, work together smoothly, and manage content easily with a Kanban board, commenting, and notifications.
### Workflow stage
A workflow is the lifecycle of a content item from its creation until it's done and ready to be published.
Explore the below workflow stages to leverage its power in Prepr.

#### To do
When you create a new content item, it is first set to the *To do* stage. This means that you have yet to start work on this content item. *To do* content items are typically items in which raw text has been entered with many missing fields.
#### In progress
The moment the content item is further compiled by you or your team, the stage is updated to *In progress*. The content item is built and all fields are now filled with the correct content. You can save content items in this stage, even if required fields have not been entered.
#### Review
When the content item is almost finished and needs a final check, set the workflow stage to *Review*. If someone else has to review your work, you can [assign this content item to someone else](#assignees). You can save content items in this stage, even if required fields have not been entered.
#### Done
The content item is finished and does not need any further adjustments. The content is set to *Done* automatically when the content item is published. Check out the [Manage content items doc](/content-management/managing-content/managing-content-items) for more details. You can only save content items in the stage *Done* if all required fields have been entered.
#### Archived
A content is automatically *Archived* when the *Unpublish on* date and time is set on the content item and has been reached.
#### Custom workflow stages
The workflow stages listed above are predefined in your Prepr environment.
If you need additional stages to match your content creation process such as a *Translate* stage, your environment administrator or owner can add a custom workflow stage.
Check out the [environment settings](/project-setup/setting-up-environments#workflow-stages) for more details.
### Assignees
You can assign content items to a user and filter the content item list by assignee, making it easier to focus on the items you need to work on.
To assign a content item to another user, follow the steps below.
1. Go to the **Content** tab and select the content item you want to assign.
2. Simply click the *Assigned to \[Name]* or *Unassigned* text at the top of the page to open the user search bar.
3. Search for and choose the user you want to assign the content item to. You can assign the content item to yourself or to another user.

To get a clear overview of all content items that have been assigned to a specific user, you can filter the content item list by the *Assignee*.
Click the user for whom you want to see all content items. Select *Assigned to me* if you want to see all content items that you have to work on.
### Commenting
You can make internal comments for a content item, for example, while reviewing the content. Follow the steps below to add a comment.
1. Go to the **Content** tab and select the content item you need to review.
2. Hover over the applicable field you want to comment on and click the icon to add a new comment.
3. To notify another user, you can mention them in your comment with an `@`, type their first initial and choose their name from the list of users.

4. Click the **Post comment** button to save your comment.
Your co-worker will receive an email [notification](#notifications) and a notification in Prepr at the top of the screen with a link to the right content item.
Once you've added one or more comments to a content item, the *Review comments* icon is visible at the top of the content item.
Any users who have access to commenting can then view, react to and resolve your comments.
### Kanban view
At the top right of the content item list, switch from the *List view* to the *Kanban* view.
Here you can see the content items by each of the workflow stages. In this view, you can easily drag and drop a content item to update the stage.

### Notifications
When you share or assign a content item to someone else or mention them in a comment, they will receive a notification in Prepr at the top of their screen and a notification by email within a few minutes. The link in the email will lead them directly to the right content item.

When another Prepr user starts working in the same content item as you, this will be visible above the save button.

Source: https://docs.prepr.io/content-management/collaboration
---
# Content management
Discover all you need to know about managing content items, how to handle assets, localizing content, and collaboration when working with content items in Prepr CMS.
Source: https://docs.prepr.io/content-management
---
# Data collection fundamentals
*In this guide, you'll find essential concepts about data collection for Prepr CMS.*
## Introduction
Data collection is the process of tracking and recording information about how web app visitors engage with content.
The data gathered helps you understand visitor behavior, optimize content, and drive business decisions.
The key concepts below provide a clear understanding of what data collection means for a front-end application that uses Prepr CMS.
## Event-based system
{/* Prepr CMS offers several data-driven features such as personalization, A/B testing, and recommendations.
To power these features, Prepr requires visitor data.
This data is essential for creating segments for personalization, evaluating A/B test results, and determining relevant recommendations. */}
Prepr CMS is an event-based system, offering powerful insights into web app visitor interactions.
You can therefore capture these interactions (events) as they happen. Each event represents a distinct action, such as viewing content, clicking a button, or making a purchase.
Now that you understand how important events are in Prepr CMS, let's look at an event more closely.
## Anatomy of an event
An event consists of three components: the content item, the action performed, and the visitor performing the action in the web app.
Let's take a look at an example where visitor *12345* views the home page of your website.
*12345* is the visitor, the *Home page* is the content item, and viewing is the action.

By understanding who did what and with which content, you collect the data you need for A/B testing, personalization and recommendations.
In the above example, a view action is the event type. Let's go a step further and look at different event types.
## Event Types
Different types of events track different ways that visitors interact with a web app.
Categorizing events allows you to filter and analyze data more effectively.
Check out [how to record events in Prepr](/data-collection/recording-events) for more technical details.
|Event type| Definition|
|----------| ----------|
|[View](/data-collection/recording-events#view)| You can use a view event to record a visitor viewing a content item, like a home page or an article, or when a visitor scrolls to a specific element in a page, like a testimonial. |
|[Like](/data-collection/recording-events#like)| You can use a like event when a visitor clicks a link or button to indicate their approval or interest. When the same visitor clicks to undo their like, you can then record the *Unlike* event. |
|[Bookmark](/data-collection/recording-events#bookmark)|A bookmark event is typically when a visitor clicks a link or button to save a piece of content to view later. When the same visitor clicks to remove their bookmark, you can then record the *Unbookmark* event.|
|[Subscribe](/data-collection/recording-events#subscribe)|A subscribe event is usually when a visitor clicks a link or button to subscribe to a newsletter, content updates, or other recurring content notifications. When the same visitor clicks to cancel their subscription, you can then record an *Unsubscribe* event.\
|[Sign up](/data-collection/recording-events#signup)|You can use a sign up event when a visitor creates a new account or registers on your web app. This event is not related to any content item, but to a specific visitor. This event is useful when your web app uses an identity provider to manage visitors.|
|[Custom events](/data-collection/recording-events#recording-custom-events)|If you have another type of interaction that you want to track and record to segment visitors, you can record a custom event in Prepr, such as a *Purchase* event. |
## Data collection and personalization
Data collection enables the [creation of visitor segments](/personalization/managing-segments) for personalized content.
By collecting event data related to visitor behavior, you can identify patterns and group users into segments with similar interests or behaviors.
By segmenting visitors based on their event data, you can offer tailored content, improving relevance and boosting performance metrics.
For example, a virtual car leasing company (*Acme Lease*) wants to track and record when visitors view content items related to electric cars, like their *Electric car landing page*.
To do this, they send a *View* event to Prepr for each visitor who opens the *Electric car landing page*.
They can then choose to create a segment for visitors who prefer electric cars. Check out the [setting up personalization guide](/personalization/setting-up-personalization) for more details.
## Data collection and A/B testing
A/B testing involves comparing two variants of a web page to evaluate which performs better.
The variants of an A/B test are typically measured by impressions, when visitors view certain elements on a page, and conversions, when visitors click certain links or buttons.
A/B testing helps you make data-driven decisions.
By evaluating how different variants impact visitor behavior, you can optimize experiences, increasing conversions and engagement.
When you want to create metrics for impressions and conversions in Prepr, you can set up special attributes in the HTML of your web pages.
- One attribute defines an impression on an element in the web page, like the header. When a visitor views this element, an impression is then automatically sent to Prepr.
- Another attribute defines a conversion for a button or link in that element. When a visitor clicks this link or button, a conversion is automatically sent to Prepr.
Check out the [A/B testing guide](/ab-testing/setting-up-ab-testing#track-impressions-and-conversions-for-stack-field-ab-test) for more details.
## Data collection and recommendations
Event data can be used to generate personalized recommendations based on content popularity and visitor behavior.
By analyzing which content items are frequently viewed together, you can make relevant suggestions to visitors.
Recommendations drive engagement by helping users discover content that aligns with their interests.
Popularity-based recommendations, powered by event data, help visitors find what others have enjoyed, increasing overall satisfaction and time spent on the platform.
Prepr automatically generates recommendations for *People Also Viewed* and *Popular items* lists when you track and record view events.
You can then easily [retrieve recommendations from the API](/recommendations) for these lists.
Now that you understand the key data collection concepts, check out the [step-by-step guide](/data-collection/step-by-step-guide) on how to collect data.
Source: https://docs.prepr.io/data-collection/fundamentals
---
# Step-by-step data collection guide
*This guide takes you through the high level steps to implement data collection for Prepr CMS.*
## Collecting visitor data in Prepr
You can gather valuable insights from your visitors by setting up efficient data collection in Prepr CMS.
Follow the steps below to get these insights and use them to optimize your web app.
We trust that we've guided you to set up data collection in Prepr CMS. Are you missing some resources or specific info? [Please let us know](https://forms.gle/B4SezB1rhzEqeGLe8).
Source: https://docs.prepr.io/data-collection/step-by-step-guide
---
# Setting up Prepr tracking
*This guide takes you through installing Prepr tracking, testing this setup, and additional tracking options.*
## Introduction
Installing Prepr tracking allows you to capture visitor data and lets you track how visitors engage with your content.
This is an essential step when setting up personalization, A/B testing and recommendations.
Even before implementing personalization, it also helps your digital teams gather useful insights for [segmentation](/personalization/managing-segments).
If you haven't yet done so, check out the [Fundamentals guide](/data-collection/fundamentals) for key data collection concepts.
And for an overview of the high-level steps needed for data collection, check out the [Step-by-step guide](/data-collection/step-by-step-guide).
The steps below show you how to enable Prepr Tracking in your web app and start collecting data right away.
## Enabling Prepr tracking
Prepr uses a lightweight piece of JavaScript to capture visitor data and events.
Follow the steps below to enable Prepr tracking in your web app.
1. In your Prepr environment, click the icon and choose the **Event management** option.
2. Click the **Get tracking code** link and copy the *Prepr Tracking Code*.

3. Add the copied value to the `` section of your web-app like in the example below.
Once you've set up Prepr tracking in your front-end app, you can then [add simple meta tags](/data-collection/recording-events#initial-setup-with-meta-tags) to your web app to track events for visitors on specific content items.
## Testing event tracking
Before sending event data to Prepr, it's important to test that the above setup was done correctly.
You can test it directly in your browser with the steps below.
1. Simply navigate to any page in your web app.
2. Right-click anywhere in the page and click the *Inspect* option.
3. Click the *Network tab* and refresh the content page.
4. Choose the **All** button and enter *pixel.gif* in the *Filter* textbox.

You'll see the tracking request with a `200 OK` status.
Great! Prepr tracking is installed successfully. You are ready to [record some events](/data-collection/recording-events) depending on your needs for personalization and A/B testing.
## Sending events to Google Tag Manager (GTM)
To send experiment-related events (such as impressions of content variants, A/B experiments, or segmented views) to Google Tag Manager, you can enable the built-in GTM integration in the [Prepr Tracking Code](#adding-the-tracking-code).
This allows you to reuse the data collected by Prepr for reporting and personalization pipelines in Google Analytics 4 (GA4) or other GTM-connected services.
To activate this, you simply need to update your `prepr("init")` function in the *Prepr Tracking Code* with the *googleTagManager* destination flag:
```js copy {4-8}
prepr(
"init",
"abc123ecfff2385c9c4fe027d56ea7d544cee78b173",
{
destinations: {
googleTagManager: true
}
}
);
```
Once this is enabled, Prepr automatically sends personalization events to *GTM dataLayer* under the name `prepr_personalize`.
```json copy
{
event: 'prepr_personalize',
event_name: 'Impression', // or Click, Conversion, etc.
prepr_experiment: 'experiment-id',
prepr_item: 'content-item-id',
prepr_variant: 'variant-id',
prepr_segment: 'CUSTOMERS_FROM_GERMANY' // or 'ALL_OTHER_USERS' for control
}
```
## Additional configuration options
You can optionally add the following options to the tracking pixel:
### variantImpressionThreshold
This setting ensures that impression events for personalized variants are only triggered if the content stays in view for a minimum duration. This could help filter out accidental or too-short impressions, improving event quality
```js copy {4-6}
prepr(
"init",
"abc123ecfff2385c9c4fe027d56ea7d544cee78b173",
{
variantImpressionThreshold: 2000 // in milliseconds (e.g., 2000 = 2 seconds)
}
);
```
**Default**: 0 (fires always when a variant leaves the viewport)
### setCookieSecure
This boolean setting allows you to flag the tracking pixel's cookies as secure when it's set to `true`.
A secure cookie is only sent over encrypted connections, such as HTTPS.
In other words, the true `setCookieSecure` setting instructs the browser to only include it in requests that use the `https:` protocol.
```js copy {4-6}
prepr(
"init",
"abc123ecfff2385c9c4fe027d56ea7d544cee78b173",
{
setCookieSecure: true
}
);
```
This prevents attackers from intercepting the cookie through eavesdropping on unencrypted HTTP connections.
## Excluding IP addresses
Sometimes you want to make sure that internal IP addresses are not tracked.
If you can exclude interactions from internal IP addresses, it makes the insights you gain more realistic and calculates more accurate metrics.
You can exclude these IP addresses directly in Prepr.
Follow the steps below to exclude IP addresses.
1. Click the icon and choose the **Event management** option to open the Event management page.

2. Scroll down to the *Exclude IP addresses* section and enter the list of IP addresses you want to exclude from data collection.
Now that you've set up Prepr tracking, you can [record events](/data-collection/recording-events).
Source: https://docs.prepr.io/data-collection/setting-up-the-tracking-code
---
# Recording events
*This guide walks you through how to record event data in Prepr.*
## Introduction
Tracking how visitors interact with your content is crucial for gaining insights and optimizing their experience.
You can also track events to store visitor profile data when needed.
You can easily track and send events to Prepr, such as views, likes, subscriptions, and custom events, directly from your front end.
If you haven't yet done so, check out the [Fundamentals guide](/data-collection/fundamentals) for key data collection concepts.
Before recording events, make sure you’ve already [enabled Prepr tracking in your web app](/data-collection/setting-up-the-tracking-code).
## Adding meta tags
By adding meta tags to your front end, you can define the content items or identity provider IDs for your visitors without needing to define them for every event you record.
### Tracking content items
To start tracking events on your content items, add one of the following meta tags to the `` section on these content pages:
Set the `content` value in the property or name tags to the *Item ID* of the content item, such as a *Home page*.

If Prepr tracking is set up and you've added a meta tag for specific content items, you can test this setup with the following steps:
1. Simply navigate to the page in your web app where you've included the meta tag above. This automatically triggers a view event that gets sent to Prepr.
2. Log in to your Prepr environment that your web app is connected to.
3. Go to the **Segments** tab.
4. Click the visitor at the top of the list. The *Date* value will match the time that you opened the page.
5. On the visitor detail page, you'll see a *View* event in the events table, and the details should match the content item that you added in the meta tag.

Now, let's look at how to set up identity provider IDs for visitors who trigger events.
### Tracking identity provider IDs
As you've seen in the [fundamentals guide](/data-collection/fundamentals#anatomy-of-an-event), a visitor is linked to an event.
When tracking events using the tracking pixel, Prepr automatically links the event to a visitor by generating a unique ID and storing it in the `__prepr_uid` cookie.
This default behavior simplifies your code when recording events in Prepr.
On the other hand, if your site uses an identity provider to manage your visitors, you can add a meta tag to the HTML of your web app to link the identity provider ID to any event that occurs in the session.
Replace the `content` value in the example code below with the known visitor ID from your identity provider.
```html copy
```
If the `VISITOR_ID` you provide does not exist in Prepr when the event gets processed, a new visitor profile will be created automatically.
Now that you know how to link visitors to events, let's look at how to record specific events.
### Tracking for multi-locale environments
If you're running a multi-locale website, add the locale meta tag to indicate the locale you want to track. For example, to track A/B tests on text fields.
```html copy
```
## Recording predefined events
There are two types of predefined events you can record.
- Events for specific content items to gain insights and optimize visitor experience.
- Events to store visitor characteristics for specific visitors.
Prepr recognizes each of the events below and processes them differently according to their purpose.
### View
You can use this event type when you want to record when a visitor opens a web page or when a visitor scrolls to an element in a web page.
If you want to record a view event for an entire page, [add the content item meta tag](#tracking-content-items) to the HTML in your page to automatically send the `View` event to Prepr when the page loads.
If you want to record a view event for a content item embedded in a page, you can send the event using the following javascript function:
```js copy
prepr('event', 'View', { 'id': "{{CONTENT_ITEM_ID}}" });
```
See some example code below for when the visitor moves their mouse over the footer.
Replace the `{{CONTENT_ITEM_ID}}` with the *Content item ID* of the footer in this case.
### Like
You can use the `Like` event to indicate when a visitor clicks a link or button to indicate their approval or interest.
Send a like event from your front-end with the following javascript function:
```js copy
prepr('event', 'Like');
```
See some example code below for when a visitor clicks the **Like** button.
If a `Like` event is sent to Prepr more than once, the subsequent events are simply ignored.
If you want to indicate that a visitor likes a specific content item in the page, you can send the relevant *Content item ID* in the event.
```js copy
prepr('event', 'Like', { 'id': "{{CONTENT_ITEM_ID}}" });
```
If you don't include a *Content Item ID* in the send request, the event will be linked to the content item in the [content item meta tag](#tracking-content-items) you set up earlier.
You can also undo the `Like` event by sending a corresponding `Unlike` event for the same visitor and content item.
```js copy
prepr('event', 'Unlike');
```
### Bookmark
You can use the `Bookmark` event to indicate when a visitor clicks a link or button to save a piece of content to view later.
Send a bookmark event from your front-end with the following javascript function:
```js copy
prepr('event', 'Bookmark');
```
See some example code below for when the visitor clicks the **Bookmark** button.
If a `Bookmark` event is sent to Prepr more than once, the subsequent events are simply ignored.
If you want to indicate that a visitor likes a specific content item in the page, you can send the relevant *Content item ID* in the event.
```js copy
prepr('event', 'Bookmark', { 'id': "{{CONTENT_ITEM_ID}}" });
```
If you don't include a *Content Item ID* in the send request, the event will be linked to the content item in the [content item meta tag](#tracking-content-items) you set up earlier.
You can also remove the `Bookmark` event by sending a corresponding `Unbookmark` event for the same visitor and content item.
```js copy
prepr('event', 'Unbookmark');
```
### Subscribe
You can use a `Subscribe` event to indicate when a visitor clicks a link or button to subscribe to a newsletter, content updates, or other recurring content notifications.
Send a subscribe event from your front-end with the following javascript function:
```js copy
prepr('event', 'Subscribe');
```
See some example code below for when the visitor clicks the **Subscribe** button.
If a `Subscribe` event is sent to Prepr more than once, the subsequent events are simply ignored.
If you want to indicate that a visitor likes a specific content item in the page, you can send the relevant *content item ID* in the event.
```js copy
prepr('event', 'Subscribe', { 'id': "{{CONTENT_ITEM_ID}}" });
```
If you don't include a *Content Item ID* in the send request, the event will be linked to the content item in the [content item meta tag](#tracking-content-items) you set up earlier.
You can also cancel the `Subscribe` event by sending a corresponding `Unsubscribe` event for the same visitor and content item.
```js copy
prepr('event', 'Unsubscribe');
```
### SignUp
You can use a `SignUp` event to track when a visitor creates a new account or registers on your web app.
This event is not related to a specific content item, but to a specific visitor.
In this case, make sure to [add the visitor meta tag](#tracking-content-items).
Send a `SignUp`event from your front-end with the following javascript function:
```js copy
prepr('event', 'SignUp');
```
See some example code below for when the visitor clicks the **Sign Up** button.
If a `SignUp` event is sent to Prepr more than once, the subsequent events are simply ignored.
Unregistered visitors are automatically deleted from Prepr after 90 days of inactivity.
The `SignUp` event ensures the visitors who've signed up will not be deleted.
### Tag
Similar to the events listed above, you can also trigger visitors to volunteer data about themselves through your web app.
This could be characteristics like their job title or industry they work in.
Knowing if a visitor is a developer, for example, can help you direct them to more technical content.
For example, when a visitor fills a specific value in a form on your page, you can send the entered value as a `Tag` on the visitor profile.
Use the javascript function below to store a tag for the visitor, for example, to indicate that they are a health care worker.
```js copy
prepr('event', 'Tag', ['healthcareworker']);
```
In the example code below we send a `Tag` to indicate when the visitor chooses a job title from a drop-down field.
### Email
Similar to the `Tag` event above, you can trigger an event to store the email address of a visitor.
For example, when a visitor fills in a form on your page, you can add the entered email address to the visitor profile.
This makes it easier to track the visitor events in other platforms you might be using.
Use the javascript function below to store the email address for the visitor.
```js copy
prepr('event', 'Email', 'jesse.ward@acme-company.com');
```
## Recording custom events
You can enhance your A/B tests and adaptive content by setting up custom events in your web app.
For example, if you want to track when a visitor makes a `Purchase` or clicks to `Start` a video or livestream.
### Define conversion metrics
You can use custom events to set up conversions in your web app to get more valuable metrics for A/B tests or adaptive content.
To do this, simply [add HTML attributes to your A/B tests](/ab-testing/setting-up-ab-testing#track-impressions-and-conversions-for-stack-field-ab-test) or [when you set up personalization](/personalization/setting-up-personalization#track-impressions-and-conversions).
By adding these HTML attributes, these custom events are automatically stored in Prepr.
Once done, marketers can then define customer segments and goals using these custom events.
### Record custom events
Depending on the purpose of your custom events, these can be recorded in Prepr in different ways:
If you need to record custom events for [customer segments](/personalization/managing-segments) or [goals](/personalization/defining-goals), for instance, you can send a *Custom event* with the `event` method.
If you've already [used custom events to define conversions](#define-conversion-metrics), then you don't need to add the below javascript function.
```js copy
prepr('event', '{{CUSTOM_EVENT_NAME}}');
```
In the example code below we send a `Purchase` event when a visitor completes a purchase in the web app.
Before recording custom events, take note of the following rules when naming your custom events.
#### Naming rules
You can choose any name for the custom event that meets the naming rules below.
- The name starts with an uppercase letter (`A–Z`).
- The name contains only letters. Any numbers, and underscores (`_`) are after the first character.
- The name does not contain any spaces, hyphens, dots, or any other special characters.
- The name cannot be any of the following reserved terms: `View`, `Like`, `Bookmark`, `Subscribe`, `SignUp`, `Tag`, `Email`, `Merge`
### Managing custom event types
You can change how custom events appear in segments, goals and in metrics in Prepr.\
This feature is useful when you want to set the display name in these screens to something clearer for marketers. For example, instead of the technical event type name *Scrolled50*, you can set it to something like *Scrolled 50% of the page*.
Follow the steps below to set the **Display name** or the **Visibility** of custom events.
1. Click the icon to open the *Settings* drop-down menu and choose the ****Event management**** option. Go to the *Event types list*.

2. To change the *Display name* of the event type, hover over the specific event type in the list and click the icon. In the dialog box, change the **Display name** value and click the **Save** button.
3. You can also deselect the **Visibility** to hide all events for a specific event type. This is useful when you and marketers no longer track and analyze outdated or unused custom events.
## Sending events to Google Tag Manager (GTM)
To send experiment-related events (such as impressions of content variants, A/B experiments, or segmented views) to Google Tag Manager, you can enable the built-in GTM integration in the [Prepr Tracking Code](#adding-the-tracking-code).
This allows you to reuse the data collected by Prepr for reporting and personalization pipelines in Google Analytics 4 (GA4) or other GTM-connected services.
To activate this, you simply need to update your `prepr("init")` function in the *Prepr Tracking Code* with the *googleTagManager* destination flag:
```js copy {4-8}
prepr(
"init",
"abc123ecfff2385c9c4fe027d56ea7d544cee78b173",
{
destinations: {
googleTagManager: true
}
}
);
```
Once this is enabled, Prepr automatically sends personalization events to *GTM dataLayer* under the name `prepr_personalize`.
```json copy
{
event: 'prepr_personalize',
event_name: 'Impression', // or Click, Conversion, etc.
prepr_experiment: 'experiment-id',
prepr_item: 'content-item-id',
prepr_variant: 'variant-id',
prepr_segment: 'CUSTOMERS_FROM_GERMANY' // or 'ALL_OTHER_USERS' for control
}
```
## Using identify providers
When a visitor signs in to your website or app and you want to add the user ID from the relevant Identity Provider to the visitor profile in Prepr you
can call the `Identify` event.
Use the javascript function below to store the external ID for the visitor.
If a visitor already exists with this external ID, the profiles will be merged.
```js copy
prepr('event', 'Identify', 'external-profile-ID');
```
## Event deduplication
When recording events, the logic checks for duplicates to ensure data accuracy and prevent skewed metrics.
If a new tracked event has the same *Event type*, the same *Content item ID* and the same web app visitor ID within 5 seconds of a recorded event, the duplicate is ignored.
This applies to both predefined events (like `View` and `Click`) and custom events.
## Maximum events per visitor
Source: https://docs.prepr.io/data-collection/recording-events
---
# Tracking data using the REST API
*This guide walks you through tracking data in Prepr using the REST API instead of using the Prepr tracking pixel.*
## Introduction
You can track and capture data to understand how visitors engage with your content.
This is an essential step when setting up personalization, A/B testing and recommendations.
Even before implementing personalization, it also helps your digital teams gather useful insights for [segmentation](/personalization/managing-segments).
[Setting up Prepr tracking](/data-collection/setting-up-the-tracking-code) is a simple way to track data in Prepr.
If you're unable to enable the Prepr tracking pixel, for example, due to restrictions, you can record events in Prepr by using the REST API instead.
If you haven't already done so, check out the [fundamentals data collection guide](/data-collection/fundamentals) to understand key concepts.
Once you have a good understanding, let's dive into recording events using the REST API.
## Recording events using the REST API
With the REST API you can send event data to Prepr. This can be convenient for offline conversions or
if you're working in a restricted environment and aren't allowed to load third-party Javascript snippets.
### JSON Structure
The table below shows the fields that are supported in the REST API.
| argument | type | required | description |
|-----------------------|-------------|----------|-----------------------------------------------------------------------|
| `label` | String | Yes | Defines the event type. |
| `timestamp` | UNIX Timestamp | | Event timestamp (default time of request). |
| **Visitor Data** | | | |
| `customer` | Array | Yes | |
| `customer.id` | String | | ID of Prepr User, your own visitor ID or the visitor's email address |
| **Content Item Data** | | | |
| `content_item` | Array | | |
| `content_item.id` | String | | Content item ID the event is recorded for. |
| `content_item.locale` | String | | Locale like `en-GB`. |
| **Metadata** | | | |
| `utm_medium` | String | | Campaign Medium |
| `utm_source` | String | | Campaign Source |
| `utm_campaign` | String | | Campaign Name |
| `utm_term` | String | | Campaign Term |
| `utm_content` | String | | Campaign Content |
| `rl` | String | | Referrer location, example: `https://prepr.io/` |
You need to supply an ID for the visitor to record the event.
If the visitor ID is not found in your visitors list when the event gets processed,
a new visitor will be created.
### Recording events
If you collected all the data, the JSON data object should be posted to the following endpoint:
`POST https://tracking.prepr.io/events`
```json copy
{
"label" : "View",
"customer" : {
"id" : "x-21381723243-PRP"
},
"timestamp" : 1609588800, // Optional
"content_item" : { // Optional for custom events
"id" : "76a5e261-ec2a-4bd2-8195-cf7e889b0a68"
}
}
```
**Request headers**
- This endpoint requires an HTTP Authorization header with an Access Token containing the `capture_publish` scope.
- Include the `Prepr-Visitor-IP` header to set the location in the visitor profile to the visitor's actual location.
If the event was successfully queued the endpoint will return a status code `202 Accepted`.
To validate your input, within a few seconds, you'll see the data reflected in the Prepr **Segments** page.
## Adding a tag
For example, when a visitor fills a specific value in a form on your page, you can send the entered value as a `Tag` on the visitor profile.
Use the request below to store a tag for the visitor, for example, to indicate that they are a health care worker.
In the example code below we send a `Tag` to indicate when the visitor chooses a job title from a drop-down field.
`POST https://tracking.prepr.io/events`
```json copy
{
"label" : "Tag",
"customer" : {
"id" : "x-21381723243-PRP"
},
"tags": [
"healthcareworker"
]
}
```
## Adding an email address
When you want to save the visitor's email address in their profile, you can send the entered value with the `Email` label.
See the example request below on how to send and store an email address in the visitor profile.
`POST https://tracking.prepr.io/events`
```json copy
{
"label" : "Email",
"customer" : {
"id" : "Adf2e64cc-1e98-4fa1-81b9-12341f88dd88"
},
"email" : "jesse.ward@acme-company.com"
}
```
Source: https://docs.prepr.io/data-collection/tracking-data-using-the-api
---
# Managing visitor data manually
*This guide explains how to perform different actions on visitor data directly in Prepr.*
## Introduction
All visitors, both anonymous and known, who interact with your web app can be stored and tracked in Prepr.
When you store visitor profiles in Prepr, you can track their interactions to understand common characteristics and behavior.
This allows you to personalize their experiences by grouping them into *Segments*. For more details, check out the [*Managing segments guide*](/personalization/managing-segments).
Manage visitors in Prepr by adding, updating, merging, exporting, anonymizing, and deleting visitor profiles.
## Adding visitor profiles
You can add visitor profiles in Prepr automatically or manually. When you [enable Prepr tracking in your web app](/data-collection/setting-up-the-tracking-code) to collect visitor data, profiles are automatically created in the system for each web app visitor, if they don't already exist,
Alternatively, you can also add visitor profiles manually in two ways.

### Import visitor profiles
To manually import multiple visitors into Prepr follow the steps below.
1. Go to the **Segments** tab.
2. In the **All visitors** page, click the **Add visitor** dropdown and choose **Import visitors** to upload a CSV file.

3. Use the table below to create the complete CSV file.
| Column title | Variable | Format |
|:-----------------------|:---------------------|:-----------------------------------------------------------------------|
| first\_name | First name | |
| last\_name | Last name | |
| email | Email address | Valid email address |
| country | Country | Only English notation allowed |
| tags | Tags | Comma-separated |
| reference\_id | Reference | Only alphanumeric characters allowed. |
After the import, you will receive an email with a summary of the following information:
- How many new visitors have been created
- How many known visitors have been updated with new information
- How many visitors were already fully present in the database
- How many rows were imported (and how many failed, reason included)
### Add a single visitor profile
To manually add one visitor profile, follow the steps below:
1. Go to the **Segments** tab.
2. In the **All visitors** page, click the **Add visitor** button to create one profile.
3. Enter the *First name* and *Last name*, if needed, and click the **Save** button.
## Updating visitor profiles
To update a single visitor profile, follow the steps below:
1. Go to the **Segments** tab.
2. In the **All visitors** page, use the *Search* or *Filter* to find the visitor that you want to update.
3. Click the visitor and click the Edit icon in the *Personal Info* section on the left and update or enter values for the **Company**, **First name**, **Last name**, **Email** or **Tags**.

4. When done, click the **Save** button.
To update multiple visitor profiles, [import them with matching email addresses](#adding-visitor-profiles) or [use the mutation API](/mutation-api/customers-create-update-and-destroy).
## Merging visitor profiles
A good visitor database is a tidy database. This means that profiles are enriched with the correct visitor data and any duplicates are removed.
You can easily merge visitor profiles directly in Prepr, if you have the necessary permissions.
In other words, you can combine duplicate profiles, where one profile contains certain data and the second profile contains other data about the same person.
To merge two profiles, follow the steps below.
1. Go to the **Segments** tab.
2. In the **All visitors** page, use the *Search* or *Filter* to find the visitor that you want to merge.
3. Click the visitor to open the *Visitor* page and click the icon to open the actions drop-down menu.

4. Choose the **Merge visitor** option to open the search dialog.
5. Enter the name or ID to find and select the matching visitor profile. Then click the **Merge** button.
When two visitor profiles are successfully merged, the fields will be updated as follows:
- All fields of which only one is allowed (such as name, or date of birth) are filled in if they are empty in the main profile.
- All fields of which more than one is allowed (such as events, email addresses, or telephone numbers) are added to the main profile.
## Exporting visitor profiles
You can export visitor profiles in two ways:
- A single visitor profile (for GDPR purposes)
- Bulk export of all visitor profiles to a CSV file.
### GDPR export
Use the **GDPR export** to give individuals the right to request a copy of any of their personal data which are being used in any way according to the *General Data Protection Regulation*.
1. Go to the **Segments** tab.
2. In the **All visitors** page, use the *Search* or *Filter* to find the visitor that you want to export.
3. Hover over the visitor to the right and click the icon.

A JSON file for this visitor will be directly downloaded to your local device.
### Bulk GDPR export
Use the bulk **Export** action to export the full Prepr visitor list to a CSV file.
1. Go to the **Segments** tab.
2. In the *Actions* section in the bottom left of the page, click the **Export** link to open the export modal.

3. Choose the fields that you want included in the export and click **Export**.
We'll process your request and send you an email when it's ready to download.
The visitor export is only available for user roles with [bulk export permissions](/project-setup/managing-roles-and-permissions#add-or-edit-roles).
## Deleting visitor profiles
You can delete visitor profiles in bulk or one at a time.
### Bulk delete visitors
Follow the steps below to select multiple visitors from the *All visitors* list and delete them.
1. Go to the **Segments** tab.
2. In the *All visitors page*, hover over and click the checkbox for the visitors you want to delete.
3. To select all visitors, click the icon at the top of the list.

4. Click the icon at the top of the list to delete all the selected visitors.
5. In the pop-up window, click the **Delete** button to confirm the action.
### Delete a single visitor profile
Follow the steps below to delete a single visitor profile from the *All visitors* list.
1. Go to the **Segments** tab.
2. In the *All visitors page*, hover over the visitor you want to delete to make the actions available.

3. Click the icon on the right to delete the highlighted visitor.
4. In the pop-up window, click the **Delete** button to confirm the action.
You can also delete a visitor profile directly in the visitor profile detail page.
1. Go to the **Segments** tab.
2. In the **All visitors** page. Use the *Search* or *Filter* to find the visitor that you want to delete.
3. Click the visitor to open the *Visitor* page and click the icon to open the actions drop-down menu.

4. Choose the **Delete** option.
5. In the pop-up window, click **Delete** again to confirm the action.
Source: https://docs.prepr.io/data-collection/managing-visitor-data-manually
---
# Privacy & Security
*The General Data Protection Regulation (GDPR) is the European Union’s legal framework for protecting personal data.
It defines how organizations must collect, process, store, and delete personal information, and gives individuals specific rights over their data.*
## Roles and responsibilities
Before getting into the practicalities for ensuring GDPR compliance, it's important to understand the key roles and responsibilities when using Prepr CMS to process personal data of web app visitors.
### Controller
Under the GDPR, you, as our customer, have the role of the controller.
This means, you decide which personal data is collected, why it's collected, and how long it's retained.
As the controller, you must ensure that there is a lawful basis for processing and that all data subject rights can be fulfilled.
### Processor
We, as Prepr, perform the role of the processor.
We process personal data only according to your documented instructions and we don't decide the purposes of processing.
As a processor, we implement appropriate technical and organizational measures, ensure confidentiality, work only with vetted sub-processors, and support you in fulfilling GDPR obligations.
## Complying with GDPR
Now that you understand your GDPR responsibilities when using Prepr CMS, follow the practical steps below to stay compliant.
Now that you know what's required, here are the Prepr tools and features to help you stay compliant.
## Using Prepr to process personal data
You can process personal data directly in Prepr via the UI, make [REST API requests](/mutation-api/customers) to fetch or update visitor profile data, and track visitors using the [tracking pixel](/data-collection/setting-up-the-tracking-code) or the [Rest API](/data-collection/tracking-data-using-the-api).
### Best practices
Before we look at specific features and tools, here are some best practices to follow.
#### Default data set
You should only collect what is strictly necessary for your web app to function or for your specific business goal.
- Prepr tracking pixel:
Prepr uses client-side data collection through a first-party tracking pixel.
This ensures data accuracy and bypasses most ad-blockers.
- Additional data: If a piece of data doesn't have a clear, documented purpose, do not collect it.
#### Retention periods
By default, we delete inactive visitors after 90 days.
You can send a [*SignUp* event](/data-collection/recording-events#signup) to make sure that visitors who’ve signed up to your web app will not be deleted.
#### Important restrictions
- No sensitive personal data: We strongly recommend against storing the following in the CMS:
- Financial information like credit card details or bank account numbers.
- Health or medical information.
- Racial or ethnic origin.
- Political opinions or religious beliefs.
- Biometric or genetic data.
- No personal data in *Assets*: Don't store personal data inside uploaded assets (like images, or PDFs).
Now that we've covered these ground rules, let's look at some features and tools we provide to help you manage these responsibilities effectively.
### Right of Access: Exporting visitor data
To support the *Right of Access*, if visitors request to access their personal data, you can either use the Prepr UI or the the Rest API to export visitor data.
To export a a single visitor profile directly in Prepr, follow the steps below.

1. Open the **Segments** tab to see *All visitors*.
2. You can search for the visitor profile by their *ID*, *email*, or *reference ID*
3. Hover over the visitor and click the icon to start the GDPR export.
A JSON file containing the full visitor record with the following details is downloaded.
- personal details
- full address
- email address
- events (such as likes, views, bookmarks)
- tags
This export can be shared with the visitor as part of a GDPR access request.
### Right to Erasure: Deleting visitor data
The right to erasure, also known as the “Right to be forgotten,” allows individuals to request the complete deletion of their personal data.
To delete a visitor directly in Prepr, follow the steps below.

1. Open the **Segments** tab to see *All visitors*.
2. You can search for the visitor profile by their *ID*, *email*, or *reference ID*
3. Hover over the visitor and click the icon to delete the visitor.
All personal data for this visitor is deleted immediately once you click the **Yes, delete** button to confirm.
This deletion is irreversible and removes the visitor’s profile, events, and all associated personal data from Prepr CMS.
### Right to Rectification: Updating visitor data
The right to rectification allows individuals to have inaccurate or outdated personal data corrected.
To update a visitor’s information directly in Prepr, follow the steps below.

1. Open the **Segments** tab to see *All visitors*.
2. You can search for the visitor profile by their *ID*, *email*, or *reference ID*
3. Click the visitor to open the profile and click the **Edit details** button.
4. Update the relevant fields, such as name, email address, or other personal details.
5. Click the **Save** button to save the changes.
The corrected data is applied immediately and reflected across all systems that rely on Prepr.
### Right to Data Portability
The *Right to Data Portability* allows individuals to obtain their personal data and reuse it for their own purposes across different services.
As described in the [*Right of Access*](#right-of-access-exporting-visitor-data) section, you can simply use the *GDPR export* feature directly in Prepr.
## How we comply with GDPR
We support GDPR compliance by implementing strong operational, security, and privacy measures as a processor. These include:
### Processing instructions only
We process data exclusively based on the your configured settings and documented instructions.
### Data security
You can find detailed information on how we ensure data security with technical and organizational measures [in our trust hub](https://prepr.io/security-and-compliance).
At a high level, this information includes the following points.
- Encryption at rest and in transit
- Access controls, such as SSO
- Logging and monitoring
- Secure infrastructure
- Backup and disaster recovery
- Regular security testing
### Sub-processors
We provide the following information on our [sub-processors](https://prepr.io/company/sub-processors).
- a list of sub-processors
- their roles and data categories
- links to their compliance policy
### EU data residency
We store and process all personal data inside the EU unless you explicitly approve otherwise.
### Data breach notification
We notify you without undue delay (target 48 hours) if any personal data breach occurs.
As an *Enterprise* service level customer, we're happy to make custom arrangements with you.
### GDPR assistance
We support you by forwarding DSAR requests, helping with DPIAs where needed, and if you're an *Enterprise* service level customer, we also provide documentation for audits and compliance checks.
### End-of-contract data handling
We ensure the deletion or return of all personal data upon termination of the agreement.
Through these measures, we ensure all processing performed on your behalf meets GDPR requirements and follows the contractual obligations in the data processing agreement.
Source: https://docs.prepr.io/data-collection/privacy-and-security
---
# How bots impact event data
## What are bots?
A search bot, sometimes called a spider, is a robot that continuously browses the internet,
usually for the purpose of building a search index or archiving websites.
Bots can either run on servers in a datacenter such as Amazon Web Services, or sometimes, on people’s personal computers
that have been infected with malware or a virus (referred to as a botnet).
Some bots, such as GoogleBot, are used for legitimate purposes such as indexing the web.
Bots can artificially inflate event data, so it’s important to be aware of their existence.
## Prepr and search bots
Prepr can detect search bots that deliberately reveal themselves. All traffic from known bots and spiders is automatically excluded.
This ensures that your Prepr data, to the extent possible, does not include events from known bots.
At this time, you cannot disable known bot data exclusion or see how much known bot data was excluded.
Known bot traffic is identified using a combination of our research and the International Spiders and Bots List.
## List of excluded bots
Below is a list of search bots and their user-agents that Prepr identifies.
| user Agent | Search Bot |
|-----------------------|-----------------------|
| 200pleasebot | 200PleaseBot
| 360spider | 360Spider
| abot | CrawlDaddy, abot
| addthis | AddThis
| adldxbot | Microsoft Bing Ads
| admantx | ADmantX Platform Semantic Analyzer
| adsbot-google | Google Adwords
| advbot | AdvBot
| ahrefsbot | Ahrefs backlinks research tool
| alexa | Alexa Crawler
| apache-httpclient | Java http library
| apachebench | ApacheBench (ab)
| apis-google | APIs-Google
| appengine-google | Google App Engine
| applebot | Apple Bot
| archive.org\_bot | Internet Archive (archive.org)
| ask jeeves | Ask Jeeves
| asynchttpclient | Java http and WebSocket client library
| awe.sm | Awe.sm URL expander
| baidu | Baidu
| bdcbot | Big Data Corp
| bingbot | Microsoft Bing
| bingpreview | Microsoft Bing preview
| bitlybot | bit.ly bot
| blekkobot | Blekkobot
| blexbot | BLEXBot (webmeup)
| bot@linkfluence.net | Linkfluence bot
| bufferbot | BufferBot
| buibui-checkbot | buibui
| butterfly | Topsy Labs
| buzztalk | buzztalk
| catchbot | CatchBot (catchbot.com)
| check\_http | Nagios monitor
| cliqzbot | Cliqzbot
| cmradar/0.1 | CMRadar/0.1
| coldfusion | ColdFusion http library
| commoncrawl | CCBot
| comodo-webinspector-crawler | Comodo
| crowsnest | Crowsnest
| curabot | cura.yt
| curl | curl unix CLI http client
| dap/nethttp | DAP/NetHTTP
| datagnionbot | datagnion.com/bot.html
| daumoa | Korean portal and search engine indexing bot
| developers.google.com/+/web/snippet | Google Plus
| diffbot | Diffbot
| digitalpersona fingerprint software | HP Fingerprint scanner
| domain re-animator bot | Domain Re-Animator Bot
| domainsbot | DomainsBot
| domaintunocrawler | DomainTuno
| dotbot | Dot Bot
| duckduck | Duck Duck Go
| elb-healthchecker | AWS ELB HealthChecker
| embedly | Embedly
| eoaagent | EOAAgent
| eventmachine httpclient | Ruby http library
| everyonesocialbot | EveryoneSocial
| evrinid | Evri bot
| exabot | Exalead's bot
| exaleadcloudview | ExaleadCloudView
| facebookexternalhit | Facebook Bot
| facebot | Facebook Bot
| feedburner | RSS bot
| feedfetcher-google | Google Feedfetcher
| findxbot | Findxbot
| flipboardproxy | FlipboardProxy
| friendfeedbot | FriendFeed
| genieo | Genieo Web filter bot
| getprismatic.com | getprismatic.com
| gigabot | Gigabot spider
| gimme60bot | Gimme60 (gimme60.com)
| gimmeusabot | Gimme60 (gimme60.com)
| go http package | Go http library
| google page speed insights | Google Page Speed Insights
| google Web Preview | Google Instant Previews crawler
| google-structured-data-testing-tool | Google-StructuredDataTestingTool
| google-structureddatatestingtool | Google-StructuredDataTestingTool
| googlebot | Google Bot
| googlestackdrivermonitoring-uptimechecks | GoogleStackdriverMonitoring-UptimeChecks
| grapeshotcrawler | GrapeshotCrawler
| gravitybot | Gravity Bot
| hatena::bookmark | Hatena::Bookmark
| heritrix | heritrix
| htmlparser | HTMLParser
| http\_request2 | HTTP\_Request2
| httpclient | HTTPClient
| https://developers.google.com/+/web/snippet | Google+ Snippet Fetcher
| hubspot | HubSpot
| ia\_archiver | Internet Archive (WayBackMachine)
| icoreservice | iCoreService
| idmarch | idmarch.org/bot.html
| inagist | URL resolver
| insieve | Insieve Bot
| insitesbot | Insitesbot
| instapaper | Instapaper
| istellabot | IstellaBot
| jack | jack
| jakarta commons | Jakarta Commons HttpClient
| java | Generic Java http library
| jetslide | Jetslide
| js-kit | URL resolver
| kemvibot | Kemvi
| kimengi | Kimengi Bot
| knows.is | knows.is
| kojitsubot | Kojitsubot
| komodiabot | KomodiaBot
| kraken | kraken
| laconica | Laconica
| libwww-perl | Perl client-server library
| lijit crawler | Lijit
| linkdexbot | Linkdex Bot
| linkedinbot | LinkedIn
| linkscrawler | LinksCrawler
| linode | Linode Longview
| lipperhey | Lipperhey
| livelapbot | Livelapbot
| loadtimebot | Load Time Bot
| longurl | URL expander service
| ltx71 | ltx71.com
| lumibot | Lumibot
| lwp-trivial | Another Perl library
| magpie-crawler | magpie-crawler
| mail.ru\_bot | Mail.ru Bot
| meanpathbot | meanpath
| mediapartners-google | Google Adsense bot
| megaindex.ru | MegaIndex
| memorybot | mignify.com/bot.html
| metauri | MetaURI
| mfe\_expand | Mcafee spider
| mir web crawler | MIR web crawler
| mj12bot | Majestic-12 spider
| mojeekbot | Mojeek UK search crawler
| mrchrome | MrChrome
| ms search 6.0 robot | MS Search 6.0 Robot
| msnbot-media | Microsoft media bot
| msnbot | Microsoft bot
| nerdybot | NerdyBot
| netcraft | Netcraft
| netstate | netEstate NE Crawler
| netvibes | Personalized dashboard bot
| netzcheckbot | netzcheck
| newrelicmonitor | NewRelic monitor
| newrelicpinger | NewRelicPinger
| newsme | newsme
| niki-bot | niki-bot
| ning | NING | Yet Another Twitter Swarmer
| nutch | Apache search spider
| openhosebot | OpenHoseBot
| orangebot | OrangeBot
| pagesinventory | pagesinventory.com
| panopta | Monitoring service
| paperlibot | PaperLi
| peerindex | peerindex
| percolatecrawler | PercolateCrawler
| perfectmarketkwtbot | PerfectMarket
| phantomjs | PhantomJS
| pingdom | Pingdom monitoring
| pinterest | Pinterest
| plukkie | botje.com/plukkie.htm
| privacyawarebot | PrivacyAwareBot
| proximic | Proximic Spider
| psbot-page | Picsearch
| publiclibraryarchive.org | publiclibraryarchive.org
| pycurl | Python http library
| python-httplib2 | Python-httplib2
| python-requests | Python http library
| python-urllib | Python http library
| queryseeker | QuerySeekerSpider
| quicklook | QuickLook
| re-animator | Domain Re-Animator Bot
| readability | Readability
| rebelmouse | RebelMouse
| redditbot | Reddit Bot
| relateiq | RelateIQ
| riddler | Riddler Bot
| rogerbot | SeoMoz spider
| rssmicro | RSS/Atom Feed Robot (rssmicro.com)
| ruby | Ruby
| scrapy | Scrapy
| screaming frog seo spider | Screaming Frog SEO Spider
| searchmetricsbot | SearchmetricsBot
| semrushbot | SEO analysis bot
| seokicks | SEOKicks
| seznambot | SeznamBot
| shopwiki | ShopWiki
| shortlinktranslate | Link shortener
| showyoubot | Showyou iOS app spider
| siege | Joe Dog Siege
| sistrix | SISTRIX
| siteuptime | Site monitoring services
| slack | Slackbot-LinkExpanding
| slackbot | Slack Bot
| slurp | Yahoo spider
| smtbot | SimilarTech
| socialrank | SocialRankIOBot
| sogou | Chinese search engine
| spbot | OpenLinkProfiler
| spider | generic web spider
| spinn3r | Spinn3r aggregator
| sputnikbot | SputnikBot
| squider | Squider
| statuscake | StatusCake
| stripe | Stripe
| test certificate info | C http library?
| tineye | TinEye Bot
| traackr | Traackr Bot
| trendictionbot | Trendiction Search
| turnitinbot | TurnitinBot
| tweetedtimes | The Tweeted Times
| tweetmemebot | TweetMeMe Crawler
| twikle | Social web search bot
| twitjobsearch | TwitJobSearch
| twitmunin | Twitmunin
| twitterbot | Twitter URL expander
| twurly | Twurly
| typhoeus | Typhoeus
| umbot | uberMetrics
| unwindfetch | Gnip
| uptimerobot | Uptime Robot
| vagabondo | Vagabondo
| vb project | Visual Basic
| vigil | Vigil
| vkshare | VKontake Sharer
| voilabot | VoilaBot
| vrcrawler | Venture Radar
| wasalive-bot | Wasalive Bots
| watchsumo | WatchSumo
| wbsearchbot | Ware Bay Best Buys
| webscout | Webscout
| wesee | WeSEE
| wget | wget unix CLI http client
| wordpress | WordPress spider
| wormly | WormlyBot
| wotbox | Wotbox
| xenu link sleuth | Xenu Link Sleuth
| xing-contenttabreceiver | Xing bot
| xovibot | XoviBot
| yacybot | YaCy
| yahoo-ad-monitoring | Yahoo Ad monitoring
| yandex | Yandex
| yeti | Naver Corp
| yourls | YOURLS
| zelist.ro | feed parser
| zibb | ZIBB spider
| zitebot | Zite
| zyborg | Zyborg
Source: https://docs.prepr.io/data-collection/search-bots
---
# Data collection
*Prepr CMS offers several data-driven features such as Adaptive content, A/B testing, and Recommendations.
To power these features, Prepr requires visitor data.
This data is essential for creating segments for personalization, evaluating A/B test results, and determining relevant recommendations.
Discover all you need to know about collecting and managing visitor data to make the most out of these features.*
Learn more about data collection key concepts and move on to the step-by-step guide to set it up.
Looking for something specific? Check out the detailed resources below.
Source: https://docs.prepr.io/data-collection
---
# Setting up personalization
*Estimated duration: 15-30 minutes*
*This guide demonstrates how to set up personalization and the related metrics in your web application using the Prepr GraphQL API.*
## Introduction
Prepr lets you create personalized experiences with *Adaptive content*.
With *Adaptive content*, content editors can make different versions of content for various visitor segments.
You can then show the right content to each visitor based on their segment giving them a personalized experience.
By doing the one-time setup detailed below, Prepr can track and measure the visitor interactions for each adaptive content element.
## Use case
Let's look at an everyday use case. The marketing team wants to increase conversions for their car leasing website.
They've decided to group their visitors by those who want to lease electric cars.
Based on this segment they add adaptive content to the home page to target these visitors.
**General website visitors**
When visitors navigate to the Acme Lease home page, they see a generic page like in the image below.

**Visitors interested in leasing an electric car**
When a visitor searches for an electric lease car and clicks an Acme Lease ad, they are directed to the electric lease landing page on the Acme Lease website.
If they visit the home page again later, they'll see content focused on electric lease cars instead of the generic home page above.

For this use case, the front-end application is set up to track each visitor's behavior based on their interests.
When they view the *Electric Lease Landing Page*, Prepr places them in the *Electric Car Buyers* segment.
The marketing team can then evaluate metrics in Prepr to measure conversions based on their goals for the adaptive content and determine how well the content performs.
## Prerequisites
To implement adaptive content for the above example, you need to have the following set up in Prepr:
- [A Prepr CMS account](https://signup.prepr.io/?plan=free)

## Set up personalization in your front end
Before setting up personalization in your front end, make sure the following items are in place:
- [Segments](/personalization/managing-segments#create-segments-based-on-prepr-data). If you've loaded the Acme Lease demo content, then the *Electric Car Buyers* segment is available with a condition to check when the Electric lease landing page is viewed.
- [Enable Prepr tracking](/data-collection/setting-up-the-tracking-code) and [add the content item meta tag](/data-collection/recording-events#tracking-content-items) to track visitors when they view pages.
Now you're ready to set up personalization in the following steps:
1. Identify visitors and link each visitor to a variant.
2. Retrieve adaptive content using the API.
3. Track impressions and conversions.
## Other use cases
Other than segments based on visitor behavior, you can set up personalization for other criteria.
### Segments from external CRM/CDP systems
It's possible to set up adaptive content based on segments maintained in other CDP/CRM systems.
In this case, you need to reference these external segments from within Prepr using the segment unique identifier from that system.
To set up personalization for external segments, follow the steps below.
1. [Create a new segment in Prepr](/personalization/managing-segments#use-external-segments-from-crmcdp-systems) and set the *ID* value to the segment unique identifier copied from your CRM/CDP system.
2. [Retrieve adaptive content using the API](#retrieve-adaptive-content-using-the-api) with one difference — pass the `Prepr-Segments` header instead of the `Prepr-Customer-Id`.
For more information, refer to our [API documentation](/graphql-api/personalization-recommendations-personalized-stack).
3. If the segment is based on external visitor profile data, ensure that your front end is connected to the external CRM/CDP/visitor identification system for data retrieval.
## Want to learn more?
Check out the following chapters:
- [Capturing event data](/data-collection/step-by-step-guide)
- [A/B testing](/ab-testing/setting-up-ab-testing)
- [Recommendations](/recommendations)
- [Connecting your front-end application](/connecting-a-front-end-framework)
## Schedule a free consultation
Do you want to get started with adaptive content but still have questions or want a demo?
[Schedule a free call](https://prepr.io/get-a-demo)
with a Prepr solution engineer.
Source: https://docs.prepr.io/personalization/setting-up-personalization
---
# Defining goals for adaptive content and A/B testing
*This guide covers everything you need to know about defining conversion goals to measure in-depth engagement based on adaptive content or A/B test content.*
## Introduction
A conversion goal is a specific, measurable action that you, as a marketer, want a web app visitor to take.
It represents a step forward in the visitor journey that aligns directly with your business objectives.
Let's take a look at a use case.
You want to increase the number of visitors who request a quote through your car leasing website.
To achieve this goal, you take the following actions:
1. You find that the *Homepage* is one of the main entry points in the visitor journey that results in a visitor requesting a quote.

2. So, you come up with a plan to improve the content in the hero section of the *Homepage*.
- You decide to group visitors by those who want to lease electric cars.
- You prepare personalized content for this targeted audience to drive more visitors toward the goal of requesting a quote.

3. To determine how well this personalized variant of the Hero section performs, you add it as adaptive content with the following goals:
- Micro-conversion goal: You want to measure how many visitors click the **Find your car** button on the home page because it's the start of the visitor journey to reach the end goal.
- Macro-conversion goal: You want to measure how many visitors request a quote within a day of visiting the home page.
Let's dive into how to define goals in Prepr.
## Defining conversion goals
You typically define conversion goals in the context of adaptive content (personalization) or A/B tests.
Let's define goals based on the use case above.
1. Go to **Segments → Goals**.
2. Click the **+ Add goal** link and set a condition like in the example below.

You can set up any combination of conditions in your goals for visitor [*Events*](#events).
Combine conditions by using the `AND` or `OR` logical operators.
3. Click the **Save** button and provide a meaningful name for the goal.
4. Click the **Save** button.
Now that you've seen how to define a goal, let's look at the filter options in more detail to help you set up all kinds of conditions.

When you set up conditions to define goals, you can choose an action related to *Events*.
Events refer to how the visitor interacted with content items rendered by your web app, such as **Viewed**, **Liked**, **Clicked**, **Bookmarked**, and **Subscribed**.
You can also choose a **Custom event** for any other event specific to your metrics needs.
These [events need to be recorded](/data-collection/recording-events) in the front end to track and measure these conversions accurately.
**Content item selection**
When you use one of the non-custom events, you can then choose from the following list of options to define the set of content items that you want to include in the condition.
- *Any item* - The default option. Include visitors who performed events on any content item.
- *Specific items* - Only include visitors with events on one or more specific content items. Choose the content item by its *Title* for this option. For example, *Visitors who* `did` `view` `specific items` `Landing Page Electric Car` `at least once`.
- *Items of a model* - Only include visitors with events on content items for this model. For example, *Visitors who* `did` `view` `items of a model` `Article` `at least once`.
- *Items with specific tags* - Only include visitors with events on content items with one or more tags. For example, *Visitors who* `did` `view` `items with specific tags` `Article` `at least once`.
- *Items with specific reference to item* - Only include visitors with events on content items linked to specific content items. For example, *Visitors who* `did` `view` `items with a reference to` `John Smith` `at least once` can group visitors who viewed articles written by the person, *John Smith*.
When adding a new condition for an event, the `did` action is selected by default.
You can also choose the `did not` action, instead.
For example, *Visitors who* `did not` `view` `items of a model` `Article` `at least once` `in the last quarter`.
**Frequency options**
You can further narrow your condition by specifying how many times an event occurred. Use one of the following options:
- *At least once* - The default value.
- *x number of times* - The event must occur exactly *x* times where *x* is a whole number.
- *More than x times* - Only include events that happened more than *x* times where *x* is a whole number.
- *Less than x times* - Only include events that happened less than *x* times where *x* is a whole number.
**Time filter**
You can the narrow your condition to only include visitors with recent activity (*Events*) by specifying when last the visitor interacted with a content item.
You can choose from one of the time filters below.
- *In the last day*
- *In the last week*
- *In the last month*
- *In the last quarter* (The default value)
Once, you're satisfied that your goals are well-defined, you can then link them to your adaptive content or A/B test.
## Link goals to experiments
You can link goals to either [adaptive content](/personalization/managing-adaptive-content#add-an-adaptive-content-element) or [A/B test experiments](/ab-testing/running-ab-tests#add-the-ab-test-to-a-stack-element).
If you already have adaptive content or A/B test in your content items, follow the steps below to link the relevant goals.
1. Go to the **Content** tab, and click to open the content item with adaptive content or an A/B test.
2. Go to the adaptive content or A/B test block and click the icon in the block and choose the **Manage settings** option.

3. Set the **Primary goal** value to the goal you previously defined (usually for a macro-conversion).
4. Set the **Secondary goals** value to another goal (usually for a micro-conversion).
5. Keep the default **Time window** of *1 day*, for example to measure the number of visitors achieved the goal within one day of viewing the A/B test content or adaptive content.
Now that you've linked the goal, Prepr can calculate metrics for that goal.

## What’s next?
Follow our [Managing adaptive content guide](/personalization/managing-adaptive-content) to immediately start delivering a personalized visitor experience to your website app visitors
or our [Running A/B tests guide](/personalization/defining-goals) to evaluate which variants of content A or B performs better.
Source: https://docs.prepr.io/personalization/defining-goals
---
# Managing segments
*This guide covers everything you need to know about segments including how to manage them to unlock the full potential of Prepr’s personalization features.*
## Introduction
*Segments* are groups of visitors who share similar characteristics or behaviors such as viewing, clicking, or liking content items.
By creating segments, you can target different audiences, making it easier to deliver relevant content and experiences to your web app visitors.
Before creating segments in Prepr, it's important to collect visitor data to identify target audiences.
Check out the [data collection docs](/data-collection/step-by-step-guide) for more details.
If Prepr is not your primary source for visitor profiles, you could use profile data maintained in an external CRM/CDP system with [external segments](#using-external-segments) or make use of a visitor identification integration like [HubSpot](/integrations/hubspot).
Before diving into how to build segments in Prepr, first make sure to define a segment context so that editors can use the AI feature to create personalized variants.
## Defining segment context
To get the best results from the AI feature that [generates personalized variants](/personalization/managing-adaptive-content#generate-ai-personalized-variants), define context for each segment used to personalize content.
To define this context, you can add it directly when [creating a segment](#building-segments) by generating the **AI context** after you save your conditions:

1. Simply click one of the buttons, **Claude**, **Gemini**, or **ChatGPT** to open up your preferred agent automatically or
click the button to copy the prefilled prompt in your segment settings and paste it into your preferred AI tool to request it to generate relevant segment context.
2. Copy the detailed context from your AI tool and click the **Paste** button to fill the **AI context**.
3. Review the text for accuracy and click the **Save** button.
For existing segments, follow the steps below.
1. Go to the *Segments* page.
2. Click the segment you want to update from the left-side menu.

3. Click the **Settings** button to open the segment settings.
4. Click one of the buttons, **Claude**, **Gemini**, or **ChatGPT** to open up your preferred agent automatically or
click the button to copy the prefilled prompt in your segment settings and paste it into your preferred AI tool to request it to generate relevant segment context.
5. Copy the detailed context from your AI tool and click the **Paste** button to fill the **AI context**.
6. Once done, click the **Save** button.

Now, you'll get more relevant results when creating AI variants in your content item.
## Building segments
If you collect and store visitor profiles in Prepr, you can build segments using this visitor data.
For example, you can segment visitors based on personal characteristics such as where they live, events such as their page views or clicks, or UTM parameters such as the marketing campaign that routed them to your web app.
Let's look at a virtual car leasing company (*Acme Lease*) as an example.
While analyzing visitor data, they realize that some visitors are mainly interested in electric cars.
So, they create a segment with conditions to match their behavior.
To build this segment, follow these steps:
1. Go to **Segments → + Add segment**.
2. Set up segment conditions. For our car leasing example, the condition could be *Visitors who* `did` `view` `specific items`, `Electric Lease Landing Page`, `at least once` `in the last quarter`.

You can set up any combination of conditions in your segments for [*Events*](#events), visitor [*Characteristics*](#characteristics), or the visitor [*Source*](#source).
Combine conditions by using the `AND` or `OR` logical operators.
3. If needed, you can also set up *Context* information, for example, to limit the segment to a device the visitor uses when they interact with the personalized content.
4. Click the **Save** button and provide a meaningful name for the segment. The value of the *API ID* field will be generated automatically based on the segment name. You can update the value manually if needed, like in the case of an [external segment](#using-external-segments).
5. Fill the **AI context** to help generate more relevant variants when using AI.
6. Click the **Save** button.
Now that you've seen how to create a segment, let's look at the filter options in more detail to help you set up *Conditions* and the *Context* for your segments.
## Conditions
When you set up conditions to build your segments, there's an extensive list of *Filter options* available.
The filter options are grouped into *Events*, visitor *Characteristics* and the visitor *Source*.
If you have a CRM or sales platform integration enabled, then you'll also see filter options for the applicable system such as *HubSpot*, *Leadfeeder*, *Leadinfo*, *ProspectPro* or *Snitcher*.
Let's look at these options in more detail.
### Events
Events refer to how the visitor interacted with content items rendered by your web app, such as **Viewed**, **Liked**, **Clicked**, **Bookmarked**, and **Subscribed**.
You can also choose a **Custom event** for any other event specific to your segmentation needs.

These [events need to be recorded](/data-collection/recording-events) from the front end to segment the visitors accurately.
Once these events are recorded, Prepr automatically adds those visitors to your segments according to the *Event* conditions you set up.
#### Content item selection
You can choose from the following list of options to define the set of content items that you want to include in the condition.

- *Any item* - The default option. Include visitors who performed events on any content item.
- *Specific items* - Only include visitors with events on one or more specific content items. Choose the content item by its *Title* for this option. For example, *Visitors who* `did` `view` `specific items` `Landing Page Electric Car` `at least once` `in the last quarter`.
- *Items of a model* - Only include visitors with events on content items for this model. For example, *Visitors who* `did` `view` `items of a model`, `Article`, `at least once` `in the last quarter`.
- *Items with specific tags* - Only include visitors with events on content items with one or more tags. For example, *Visitors who* `did` `view` `items with specific tags`, `Article`, `at least once` `in the last quarter`.
- *Items with specific reference to item* - Only include visitors with events on content items linked to specific content items. For example, *Visitors who* `did` `view` `items with a reference to` `John Smith` `at least once` `in the last quarter` can group visitors who viewed articles written by the person, *John Smith*.
When adding a new condition for an event, the `did` action is selected by default.
You can also choose the `did not` action, instead.
For example, *Visitors who* `did not` `view` `items of a model` `Article` `at least once`.
#### Frequency options
You can narrow your condition by specifying how many times an event occurred. Use one of the following options:
- *At least once* - The default value.
- *x number of times* - The event must occur exactly *x* times where *x* is a whole number.
- *More than x times* - Only include events that happened more than *x* times where *x* is a whole number.
- *Less than x times* - Only include events that happened less than *x* times where *x* is a whole number.
#### Time filter
You can narrow your condition to only include visitors with recent activity (*Events*) by specifying when last the visitor interacted with a content item.
You can choose from one of the time filters below.
- *In the last day*
- *In the last week*
- *In the last month*
- *In the last quarter* (The default value)

### Source
The source includes campaigns and referrals from which a visitor was redirected.
See a complete list below for available options and corresponding examples.

#### Campaigns
Choose one of these campaign sources to segment visitors by a campaign that redirected them to your web app.
- **UTM Campaign** - *Visitors who* `have entered through` `UTM Campaign` `summer_car_lease_promo`.
- **UTM Medium** - *Visitors who* `have entered through` `UTM Medium` `email`.
- **UTM Source** - *Visitors who* `have entered through` `UTM Source` `newsletter`.
- **UTM Content** - *Visitors who* `have entered through` `UTM Content` `no_comms_button`.
- **UTM Term** - *Visitors who* `have entered through` `UTM Term` `car_lease_deals`.
For each of these campaign options, you can choose the `not` action instead.
For example: *Visitors who* `have not entered through` `UTM campaign` `summer_car_lease_promo`
#### Referrals
You can group visitors that land on your web app from another website.
- **Inital referral** - *Visitors who* `are visiting through a referral domain` `https://topautorev.com`.
Now that you know how to set up conditions for your segments, let's look at the options you can choose for the *Context*.
### Characteristics
Visitor characteristics are based on data stored in the visitor profile.
Check out more details on [how to manage visitor data](/data-collection/managing-visitor-data-manually).

Let's look at each of the available characteristics for segments in more detail.
#### Country
You can choose the *Country* option to define the visitors by where they live. For the actions below, Prepr uses the *Country* in the visitor profile to match the criteria.
- *Did visit from* - For example, *Visitors who* `did` `visit from` `Netherlands`.
- *Did not visit from* - For example, *Visitors who* `did not` `visit from` `United States`.
#### Tags
Segment visitors by one of the keywords (tags) on their profile.
For example, you can choose *Visitors who* `are` `tagged with` `Employees` or *Visitors who* `are not` `tagged with` `VIP`.
Check out more technical details on [how to record these visitor properties from your front end](/data-collection/recording-events#tag).
#### Previous session
Choose one of the time filter options to segment visitors by when they last visited the web app.
- *In the last # days/weeks/months* - For example, *Visitors whose* `previous session was` `in the last 2 weeks`.
- *Before `{date}`* - For example, *Visitors whose* `previous session was` `before 7 oct 2024`.
- *After `{date}`* - For example, *Visitors whose* `previous session was` `after 1 aug 2024`.
- *Between `{date}` and `{date}`* - For example, *Visitors whose* `previous session was` `between 25 jul 2024 and 1 aug 2024`.
#### First seen
Choose one of the time filter options to segment visitors by when they first visited the web app.
- *In the last # days/weeks/months* - For example, *Visitors who* `were first active` `in the last 2 weeks`.
- *Before `{date}`* - For example, *Visitors who* `were first active` `before 7 oct 2024`.
- *After `{date}`* - For example, *Visitors who* ` were first active` `after 1 aug 2024`.
- *Between `{date}` and `{date}`* - For example, *Visitors who* `were first active` `between 25 jul 2024 and 1 aug 2024`.
#### Visits
Choose one of the options to segment visitors by how many times they visited the web app.
- *# times* - For example, *Visitors who* `visited` `5 times`.
- *More than # times* - For example, *Visitors who* `visited` `more than 10 times`.
- *Less than # times* - For example, *Visitors who* `visited` `less than 3 times`.
#### Signed up
Segment visitors by who've signed up or not signed up.
Check out [the data collection docs](/data-collection/recording-events#signup) for more details on recording this event in Prepr.
#### Email address
You can include visitors that have or don't have an email address.
### HubSpot
With this condition you can group visitors who belong to a segment defined in HubSpot, for example, for a HubSpot list based on contacts who are marked as leads.

Check out the [HubSpot doc](/integrations/hubspot) on how to activate the integration.
### Leadinfo
With this condition you can group B2B customers who belong to a segment defined in Leadinfo, for example, for visitors from the *Retail* or *Manufacturing* industries or to specifically target small to medium companies.

Check out the [Leadinfo integration guide](/integrations/leadinfo) on how to activate the integration.
### Leadfeeder
With this condition you can group B2B customers who belong to a segment defined in Leadfeeder, for example, for visitors from the *Retail* or *Manufacturing* industries or to specifically target small to medium companies.

Check out the [Leadfeeder integration guide](/integrations/leadfeeder) on how to activate the integration.
### ProspectPro
With this condition you can group B2B customers who belong to a segment defined in ProspectPro, for example, for visitors from the *Retail* or *Manufacturing* industries or to specifically target small to medium companies.

Check out the [ProspectPro doc](/integrations/prospectpro) on how to activate the integration.
### Snitcher
With this condition you can group B2B customers who belong to a segment defined in Snitcher, for example, for visitors from the *Retail* or *Manufacturing* industries or to specifically target small to medium companies.

Check out the [Snitcher doc](/integrations/snitcher) on how to activate the integration.
## Context
In addition to the conditions, you can also define a current context for the segment.
The *Context* of a segment includes current info about the visitor when they interact with personalized content.
You can select the options below to define a *Context*.
### Current country
You can define the segment in the context of the country that the visitors are currently visiting the web app from. For example, *Visitors who are* `currently visiting from` `United States`.
### Current device
You can define the segment in the context of the device that the visitors are currently using or not currently using.
For example, *Visitors who are* `currently using` `a mobile device` or *Visitors who are* `not currently using` `a mobile device`.
### HTTP header
You can define the segment in the context of an HTTP header value, such as the web app version the visitors are currently using.
For example, *Visitors who are* `currently requesting with HTTP header` `APP-Version` `is greater than 1`.
**Semantic versioning (SemVer)**
You can add conditions with semantic versions to compare more accurate version numbers like `1.0.3`.

### Current Day
You can define a segment by the current day that a visitor accesses the web app.
For example, *Visitors who are* `visiting on` `Saturday` `or` `Sunday` to target weekend visitors.
### Current Time
You can define a segment by the current time that a visitor accesses the web app.
For example, *Visitors who are* `visiting` `between 06:00 and 12:00` to target morning visitors.
Now that you know how to build segments, you're ready to manage your adaptive content. Check out the [Managing adaptive content guide](/personalization/managing-adaptive-content) for more details.
## Organize segments into folders
When you have dozens of segments, folders make it easy to find related segments.
For example, when you have multiple segments for marketers who want to track fleet customers.

Hover over your segments and click the icon.
In the *Add folder* dialog, enter a name for the new folder.
Once the folder is created, you can drag and drop the segments you want to include in the new folder.
To remove segments from the folder simply drag and drop the segments outside the folder.
Click the icon next to the folder name to either rename the folder by clicking the **Edit** option or **Delete** the folder.
When you delete the folder, it will be removed and the grouped segments will return to the alphabetical list of segments.
## Using external segments
If you manage your segments in an external system such as a CRM/CDP system and want to use them in Prepr, you can create segments that reference those external segments as follows:
1. Go to **Segments → + Create segment**. You'll notice that a default condition is partially filled.
2. Click the x icon to delete the condition and enter a name for the segment.
3. Click the **Save** button.
4. Provide a meaningful name for the segment.
5. Copy and paste the segment unique identifier from your CRM/CDP system to replace the automatically generated *API ID* value in Prepr.
6) Click the **Save** button.
This setup will work as a reference to the segment created in the external system, so you don't need to specify any conditions for this segment in Prepr.
## What’s next?
Follow our [Managing adaptive content guide](/personalization/managing-adaptive-content) to immediately start delivering a personalized visitor experience to your website app visitors.
Source: https://docs.prepr.io/personalization/managing-segments
---
# Managing adaptive content
*This guide shows you how to personalize your website to improve engagement and user experience.*
## Introduction
Prepr lets you create personalized experiences with *Adaptive content*.
With *Adaptive content*, you can make different versions of content for various visitor segments.
You can then show the right content to each visitor based on their segment giving them a personalized experience.
## Use case
Let's look at a typical use case.
You want to increase the number of visitors who request a quote through your car leasing website.
To achieve this goal, you take the following actions:
1. You find that the *Home* page is one of the main entry points in the visitor journey that starts from clicking the **Find a car** CTA button and resulting in a quote request.

2. So, you come up with a plan to improve the content in the hero section of the home page.
- You decide to group visitors by those who want to lease electric cars.
- You prepare personalized content for this targeted audience to drive more visitors toward the ultimate goal of requesting a quote.

3. To determine how well this personalized variant of the Hero section performs, you add it as adaptive content with the following goals:
- Micro-conversion goal: You want to measure how many visitors click the **Find your car** button on the home page because it's the start of the visitor journey to reach the end goal.
- Macro-conversion goal: You want to measure how many visitors request a quote within a day of visiting the home page.
**General website visitors**
When visitors navigate to the Acme Lease home page, they see a generic page like in the image below.

**Visitors interested in leasing an electric car**
When a potential customer searches for an electric lease car and clicks an Acme Lease ad, they are directed to the electric lease landing page on the Acme Lease website.
If they visit the home page again later, they'll see content focused on electric lease cars instead of the generic home page above.

For this use case, the front-end application tracks each visitor's behavior based on their interests.
When a visitor views the *Electric Lease Landing Page*, Prepr places them in the *Electric Car Buyers* segment.
This is just one way to set up adaptive content. You could also set up adaptive content based on other characteristics such as location or device.
You could even add adaptive content based on criteria outside of the web app, for example, when a user visits your web app from a social media link.
Check out the [Managing segments doc](/personalization/managing-segments) for more details.
## Prerequisites
To add adaptive content to your website like in the above example, you need to have the following set up in Prepr:
- [A Prepr CMS account](https://signup.prepr.io/?plan=free)
## Personalize your website
To personalize your website, you need to complete the following steps:
1. Set up segments based on specific visitor interaction on the content. These segments are the groups of visitors for whom you want to deliver a personalized experience.
2. Define conversion goals to get deeper insights into how well this experiment works for visitor experience.
3. Add an adaptive content element. For each segment, create content specifically aimed at those groups of visitors.
4. Manage the adaptive content by updating the adaptive content settings, if necessary.
5. Evaluate the adaptive content variants. Prepr collects metrics on the performance of the pages with adaptive content. You can use this data to adjust the content in these pages or the segments' criteria.
Congratulations, you have successfully personalized your website.
## Other use cases
This guide explains just one use case for personalization. Below we list a few more common options.
### Segments from external CRM/CDP systems
It's possible to personalize Prepr content based on segments maintained in other CDP/CRM systems. In this case, you need to reference these external segments from within Prepr using the segment unique identifier from that system.
Your personalization flow will look like this:
1. [Create a new segment in Prepr](/personalization/managing-segments#use-external-segments-from-crmcdp-systems) and set the *ID* value to the segment unique identifier copied from your CRM/CDP system.
2. [Add an adaptive content element](#add-an-adaptive-content-element).
3. The developer then [retrieves adaptive content using the API](/personalization#retrieve-adaptive-content-using-the-api) with one difference — they pass the `Prepr-Segments` header instead of the `Prepr-Customer-Id`.
### Call-to-actions
Personalizing call-to-actions can significantly increase conversion rates. Offer each segment the call-to-action that fits best. For example, show a relevant whitepaper to first-time visitors and offer a demo to returning visitors.
### Categories
Do you have content or products in different categories? Capture which categories your visitors view and display them on the homepage on their next visit.
## Want to learn more?
Check out the following guides:
- [Collecting event data](/data-collection)
- [Defining goals](/personalization/defining-goals)
- [Managing segments](/personalization/managing-segments)
- [Recommendations](/ab-testing/running-ab-tests)
## Schedule a free consultation
Do you want to get started with personalization but still have questions or want a demo?
[Schedule a free call](https://prepr.io/get-a-demo)
with a Prepr solution engineer.
Source: https://docs.prepr.io/personalization/managing-adaptive-content
---
# Personalization
*Discover all you need to know about making your website adaptive by setting up personalization, managing segments and creating adaptive content to improve engagement and user experience.*
Source: https://docs.prepr.io/personalization
---
# Setting up A/B testing
*Estimated duration: 15-30 minutes*
*This guide demonstrates how to set up A/B testing and the related metrics in your web application using the Prepr GraphQL API.*
## Introduction
A/B testing is a simple and efficient way to compare two versions of something to figure out which performs better.
In Prepr, marketers can create variants of any section or heading text field in a content item.
In your front-end app, you can then render the variants in your website to show each to separate groups of visitors.
## Use cases
Let's look at a couple of use cases.
### Test hero section on landing page
The marketing team wants to increase conversions and they've prepared two sets of content for the same section but are unsure which one will drive more visitors toward these goals.
In this case, they run an A/B test.
The image below is an A/B test example in a car leasing website.
It shows the A and B variants of the hero section on the *Electric Lease* landing page.

Once implemented, Prepr starts measuring the conversion rate for each variant and determines which is more effective.
### Test blog post headlines
The marketing team wants to increase conversions and they've prepared variants of blog post headlines, but are unsure which one will drive more visitors toward these goals.
In this case, they create an A/B test for each of the recommended blog posts.

Once implemented, Prepr measures the conversion rate for each variant and determines which headline is more effective.
## Prerequisites
To set up your front end for A/B testing according to the guide below, you need to have the following set up in Prepr:
- [A Prepr CMS account](https://signup.prepr.io/?plan=free)

## Set up your front end for A/B testing
Before setting up your front end for A/B testing, make sure to [enable Prepr tracking](/data-collection/setting-up-the-tracking-code) and [add the content item meta tag](/data-collection/recording-events#tracking-content-items) to track visitors when they view pages.
To prepare your front-end application for A/B testing, follow these steps:
1. Identity visitors and link each visitor to a variant.
2. Retrieve the A/B test content for the matching variant using the API.
3. Record custom events, if needed, for segment or goal definitions.
4. Track impressions and conversions for a stack element or for a text field.
Congratulations, you have successfully set up A/B testing in your front end. Check out the next section if you are
implementing a static website.
## Implementing A/B testing for static (SSG) websites
Unlike dynamic sites, static sites serve prebuilt content to a web browser without calls to a database. To perform
A/B testing for static site rendering, you need to set up variants ahead of time (particularly before you build the
web app) and then split traffic between different routes using the edge middleware ([see an example
tool](https://vercel.com/docs/functions/edge-middleware/quickstart)). Let’s see it in detail.
The A/B testing for static site rendering looks like this:
1. On pages where an A/B test needs to be triggered, you call the API to [pre-fetch
variants](/graphql-api/personalization-recommedations-ab-testing#pre-fetching-the-variant). Your request must include the
following fields:
- *\_context* - system field that contains additional details about variants such as the following fields:
- *kind* – returns a value of *PERSONALIZATION* or *AB\_TEST*.
- *variant* - returns a value of *A* or *B* if *kind* has a value of *AB\_TEST*
Here’s an example code snippet. Update the id string of the query with your *Content item ID* (find the ID in the
right-hand column on the *Page* content item in Prepr).
2. When a user visits your web app, you make a one-time request to know if this user is assigned to *Bucket A* or
*Bucket B*. Your request must include the following headers:
- *Prepr-Customer-ID* with a session ID of a visitor
- *Prepr-Bucket-Customer = true*
The bucketing runs at the edge location of the CDN and takes no longer than 7 ms. A response to your API request
contains a header – either *X-Prepr-Customer-Bucket A* or *X-Prepr-Customer-Bucket B*.
See an example request below:
```curl copy
curl --location --globoff 'https://graphql.prepr.io/' \
--header 'Prepr-Customer-ID: 409ad1af-d644-4d3f-8cb9-691c0318a980' \
--header 'Prepr-Bucket-Customer: true'
```
3. With this information, the front-end middleware can redirect a visitor to a respective variant of your A/B test.
For all the requests from the same visitor, the same variant will be shown.
## Want to learn more?
Check out the following guides:
- [Collecting event data](/data-collection)
* [Defining goals](/personalization/defining-goals)
- [Connecting your front-end application](/connecting-a-front-end-framework)
- [Setting up personalization](/personalization/setting-up-personalization)
## Schedule a free consultation
Do you want to get started with A/B testing but still have questions or want a demo?
[Schedule a free call](https://prepr.io/get-a-demo)
with a Prepr solution engineer.
Source: https://docs.prepr.io/ab-testing/setting-up-ab-testing
---
# Running A/B tests
*This guide shows you how to easily run A/B tests in Prepr CMS.*
## Introduction
Let's look at a typical use case for an A/B test.
You want to increase the number of visitors who request a quote through your car leasing website.
To achieve this goal, you take the following actions:
1. You find that the *Electric Lease* landing page is one of the main entry points in the visitor journey that starts from clicking the **Find a car** button and resulting in a quote request.

2. So, you come up with a plan to improve the content in the hero section of this landing page.
You prepare two variants of the hero section (variants A and B) but are unsure which one will most effectively drive visitors toward requesting a quote.

3. To determine which variant performs best, you run an A/B test with the following goals:
- Micro-conversion goal: You want to measure how many visitors click the **Find your car** button on the *Electric Lease Landing Page* because it's the start of the visitor journey to reach the end goal.
- Macro-conversion goal: You want to measure how many visitors request a quote within a day of visiting the home page.
## Prerequisites
To run A/B testing on your website like in the above example, you need to have the following set up in Prepr:
- [A Prepr CMS account](https://signup.prepr.io/?plan=free)
## Create an A/B test in Prepr
Congratulations, you have successfully implemented an A/B test and used it to improve the visitor journey on your web app.
## Want to learn more?
Check out the following guides:
- [Collect event data](/data-collection)
- [Define conversion goals](/personalization/defining-goals)
- [Manage segments](/personalization/managing-segments)
- [Personalize website](/personalization/setting-up-personalization)
## Schedule a free consultation
Do you want to get started with A/B testing but still have questions or want a demo?
[Schedule a free call](https://prepr.io/get-a-demo)
with a Prepr solution engineer.
Source: https://docs.prepr.io/ab-testing/running-ab-tests
---
# A/B testing
*Discover all you need to know about setting up and using Prepr CMS A/B testing to improve engagement and user experience.*
Source: https://docs.prepr.io/ab-testing
---
# ActiveCampaign
*In this guide, you’ll learn how to activate the ActiveCampaign integration in Prepr CMS.
This integration allows content editors to embed ActiveCampaign forms in their content items.*
## Introduction
ActiveCampaign is an all-in-one marketing and sales automation platform.
## Activating ActiveCampaign integration
That’s it. The ActiveCampaign integration is activated and content editors can now embed ActiveCampaign forms in content items.
Source: https://docs.prepr.io/integrations/activecampaign
---
# Algolia
*From this guide, you’ll learn how to index Prepr content with the Algolia search engine for an optimized and performant search experience in your web app.*
## Introduction
Prepr supports integration with [Algolia](https://www.algolia.com/), an AI-powered search service with a high-performance API.
This integration enables your Prepr content items to be indexed using the Algolia algorithm, maximizing the search speed and content discovery in your web app.
In other words, it allows your web app visitors to search across your app, easily find what they are looking for, and receive the most up-to-date content.
## Algolia integration setup
Follow the steps below to set up your Algolia integration to search Prepr content in your web app.
The basic steps show you how to connect Prepr to Algolia and to sync content items to create indexed records.
## Handling big record sizes
If you have large content items that exceed the Algolia record size limit, then you need to split up your content item records.
You can do this by updating the [index query](#define-algolia-search-indexes) you defined.
Now that you've set up the `_id` attribute, [retest your Algolia search](#test-setup-directly-in-algolia).
## FAQs
Answers to some common questions or issues when implementing the Algolia integration.
### Why do I get the following error in Algolia: *Record at the position ... is too big size=... bytes.\`*?
Your algolia record size is too big.
To resolve this, add aliases to your index query to split each content item into separate Algolia records and add an `_id` attribute in Algolia to group the records for the same content item (web page).
For more details, check out the section above on [handling big records](#handling-big-record-sizes).
### How do I create an index query for a single-item model?
When you want to make a single-item model searchable, such as an overview page, you can simply omit the `id` argument in the query like in the example below.
```graphql
query Query($locale: String!) {
EventOverview(locale: $locale) {
_slug
title
seo {
meta_title
meta_description
meta_image {
url
}
audience
}
}
}
```
### What happens when I create multiple search indexes with the same name?
In some cases you may want to create a single Algolia index for multiple models.
You do this by creating multiple queries by clicking the **+ Add search index** link each time and giving each entry the same *Algolia index name*.
When you run the content sync for one of the models that share an index name, content items for all the models in the index are synced.
This means you only need to run the content sync for one model.
Click the **Debug** button to see the request logs for all the models related to a single index..
In Algolia, the results of all the queries are merged into a single index automatically.
### How do I create a separate index for each locale?
Algolia recommends using separate indexes for each locale so you can configure locale-specific synonyms, ranking, and other settings.
To create a separate index for each locale, add `{locale}` to the *Algolia index name*. Prepr replaces `{locale}` with the locale of the content item during content sync. You only need to configure one GraphQL query for all locales.
For example, an index named `products_{locale}` creates the following indexes:
- `products_en-US`
- `products_es-US`
### Why doesn't my bulk sync work?
If your Algolia API key has index restrictions, make sure to include the temporary index. During a bulk sync, Prepr creates a temporary index using the format `_tmp_`.
For example, if your index is named `content_production`, allow both `content_production` and `content_production_tmp_*`.
### Why do temporary indexes remain after a bulk sync?
Temporary indexes are removed once the content sync completes successfully. If a temporary index remains, follow the steps below.
1. Make sure that your Algolia API key has the required ACL permissions. Missing the `settings` or `editSettings` permission is a common issue.
2. If your API key has the correct permissions, click **Debug** in your Algolia integration and check the request logs for more details.
3. If the issue persists, [contact support](https://prepr.io/support).
### How do I enable geo search?
To enable geo search, add aliases to the *Coordinates* field in the GraphQL query:
- `_geoloc`
- `lat` (latitude)
- `lng` (longitude)
For example, if your field is named `coordinates`:
```graphql /_geoloc:/ /lat:/ /lng:/
_geoloc: coordinates {
lat: latitude
lng: longitude
}
```
### How are dates and timestamps handled?
When Prepr detects an ISO 8601 date and time value, it automatically adds a Unix timestamp field with the `_timestamp` suffix. You can use this field to filter results in Algolia.
For example, `_publish_on: "2026-06-11T19:35:00+00:00"` also results in `_publish_on_timestamp: 1781206500`.
Source: https://docs.prepr.io/integrations/algolia
---
# Deprecated guide - Algolia
*From this guide, you’ll learn how to index Prepr content with the Algolia search engine for an optimized and performant search experience in your web app.*
## Introduction
If you value a good experience for your web app visitors, you might think about how they search across your app.
Can they easily find what they are looking for?
Are they receiving the most up-to-date content?
Web users have a short attention span, so it's crucial to provide them with relevant search results to maintain their loyalty.
Prepr supports integration with [Algolia](https://www.algolia.com/), an AI-powered search service with a high-performance API.
It enables your Prepr content items to be indexed using the Algolia algorithm, maximizing the search speed and content discovery in your web app.
## First things first
Note the following important factors before setting up the Algolia integration.
These give you a good understanding of the impact on record size and related cost, how records are split and the search performance.
### Record size limits
There are [record size limits](https://support.algolia.com/hc/en-us/articles/4406981897617-Is-there-a-size-limit-for-my-index-records) based on your Algolia plan.
Make sure you understand the impact of exceeding these limits before setting up the Algolia integration.
{/* Is the below callout still relevant if developers use their own GraphQL queries? */}
### Improving search results and performance
You can easily improve search results and performance by configuring Algolia attributes:
- [Searchable attributes](https://www.algolia.com/doc/guides/managing-results/must-do/searchable-attributes/)
- [Attributes for the custom ranking](https://www.algolia.com/doc/guides/managing-results/relevance-overview/in-depth/ranking-criteria/#custom)
- [Attributes for deduplication](https://www.algolia.com/doc/guides/managing-results/refine-results/grouping/)
## Setting up the Algolia integration
Follow the steps below to set up your Algolia integration to search Prepr content in your web app.
## Syncing existing content and schema updates
The setup steps above allow you to index all new content created after enabling the Algolia integration. If you need to index any content you already have, or after a schema update, you need to run the sync action manually.
To sync content items to Algolia manually, you must call the given Prepr endpoint and specify a model ID, which you can find under **Schema → Model → → Copy Model ID**.
Content items will be synced in batches, the overall time depends on the number of content items you have.
The following API request will sync content items to Algolia:
```http copy
GET: https://mutation.prepr.io/publications/algolia/sync
```
```json copy
{
"model": {
"id": "YOUR_MODEL_ID"
}
}
```
Source: https://docs.prepr.io/integrations/algolia-V1
---
# BigCommerce
*This integration allows content editors to select BigCommerce products in Prepr content items.*
## Introduction
You can connect Prepr to [BigCommerce](https://www.bigcommerce.com/), a platform including online store creation, search engine optimization, hosting, and marketing and security from small to Enterprise sized businesses.
This integration allows you to view and search the BigCommerce catalog directly in Prepr and include products in your web app content.
## Activating BigCommerce integration
That’s it. Now your web page includes catalog data from BigCommerce.
Prepr will synchronize your remote content automatically to keep it up to date.
Source: https://docs.prepr.io/integrations/bigcommerce
---
# Bynder
*In this guide, you’ll learn how to activate the Bynder integration in Prepr CMS. This integration allows content editors to access Bynder assets so teams can work with approved media from an enterprise DAM platform directly in Prepr.*
## Introduction
Bynder is an enterprise digital asset management (DAM) platform.
If your organization uses Bynder as the central source for images and other media, you can make those Bynder assets available directly in Prepr content items.
This way, content editors can work with centralized DAM assets without switching between systems.
## Using Bynder assets in Prepr
You can enable the Bynder integration to use Bynder assets in Prepr CMS by following the steps below.
That’s it. The Bynder integration lets your team work with enterprise DAM assets directly in Prepr content.
Source: https://docs.prepr.io/integrations/bynder
---
# Cloudinary
*This integration allows content editors to select media from a Cloudinary account in content items.*
## Introduction
Cloudinary provides cloud-based image and video management services. If your tech stack includes Cloudinary as your primary digital asset management system, you can use the Cloudinary-stored assets directly in Prepr. This means it's possible for content editors to use those assets in their content items.
Source: https://docs.prepr.io/integrations/cloudinary
---
# Commerce Layer
*This article describes how to add the Commerce Layer catalog data to your web application using Prepr.*
## Introduction
Prepr supports a native integration with [Commerce Layer](https://commercelayer.io/), an API-first commerce platform.
This integration allows you to view and search the Commerce Layer catalog directly in Prepr and include products in your web app content.
You can activate the Commerce Layer integration by following the steps below.
## Activating Commerce Layer integration
That’s it. Now your web page includes content inserts from Commerce Layer. In addition, Prepr will synchronize your remote content automatically to keep it up to date.
Source: https://docs.prepr.io/integrations/commerce-layer
---
# Commercetools
*This article describes how to add the Commercetools catalog data to your web application using Prepr.*
## Introduction
Prepr supports native integration with [Commercetools](https://commercetools.com/), a cloud-based headless commerce solution.
This integration allows you to view and search the Commercetools catalog directly in Prepr and include products in your web app content.
You can activate the Commercetools integration by following the steps below.
## Activating Commercetools integration
That’s it. Now your web page includes content inserts from Commercetools. Prepr will synchronize your remote content automatically to keep it up to date.
Source: https://docs.prepr.io/integrations/commercetools
---
# Customer.io
*This integration allows you to sync segments from this messaging platform.*
## Introduction
Customer.io is a messaging platform used by marketers to created automated message campaigns. If Customer.io is your primary customer data management system, then you can integrate your defined segments to Prepr.
Source: https://docs.prepr.io/integrations/customerio
---
# Form.io
*In this guide, you’ll learn how to activate the Form.io integration in Prepr CMS.
This integration allows marketers to embed interactive forms and collect responses directly in your content.*
## Introduction
Form.io is a powerful form builder that allows you to create complex forms, integrate APIs, and manage workflows.
If your tech stack includes Form.io to maintain your forms, you can add Form.io forms directly in Prepr.
Source: https://docs.prepr.io/integrations/formio
---
# Formstack
*In this guide, you’ll learn how to activate the Formstack integration in Prepr CMS.
This integration allows marketers to embed interactive forms and collect responses directly in your content.*
## Introduction
Formstack is an all-in-one platform that helps organizations to create digital forms, generate documents, and collect digital signatures.
If your tech stack includes Formstack to maintain your forms, you can add Formstack forms directly in Prepr.
Source: https://docs.prepr.io/integrations/formstack
---
# Frontify
*In this guide, you’ll learn how to activate the Frontify integration in Prepr CMS. This integration allows content editors to access Frontify brand assets to make sure that their content is brand-compliant.*
## Introduction
Frontify is a brand management platform.
If your tech stack includes Frontify as the primary system for your branded assets, you can use the Frontify-stored branded assets directly in Prepr content.
This means it's possible for content editors to use those assets in their content items.
## Use Frontify assets in Prepr
You can enable the Frontify integration to use Frontify assets in Prepr CMS by following the steps below.
That’s it. The Frontify integration is activated and your web page includes content with embedded images from Frontify.
Source: https://docs.prepr.io/integrations/frontify
---
# FTP Server
*This integration allows you to connect to an FTP server to import video and audio assets automatically.*
## Introduction
An FTP server makes files available for download via a file transfer protocol. By activating an integration to an FTP server in Prepr, assets will automatically be imported into the *Media Library* from the FTP server that you define. These assets are then available for your content editors to include them in any content items.
## Activate FTP server
Simply activate the FTP server integration with the following steps:
1. Click the icon and choose the **Integrations** option.
Go to the **FTP Server** card and click the **Activate** button.
2. Fill in the connection details of your FTP server and click the **Save** button.

To make changes to the connection details, go back to the **FTP Server** block and click the **Manage** button to make your changes.
If you have any questions, please [contact our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/integrations/ftpserver
---
# Google Workspace
*Integrate this cloud-based suite of business collaboration tools to streamline your authentication permissions in your organization.*
## Introduction
Prepr offers several ways to log in: Using a passkey, single sign-on or by submitting an email address with a password.
Single sign-on (SSO) is a way to authenticate and log in to an application with just one set of credentials, rather than having to set up multiple usernames and passwords across different platforms.
It's a more secure process and prevents potentially losing or forgetting log-in credentials since it's stored through another service.
You can integrate with Google Workspace if you want to let users sign-in through your company Google Workspace.
Check out this [step-by-step guide](/project-setup/setting-up-sso#google-workspace) on how to set up SSO with Google Workspace.
Source: https://docs.prepr.io/integrations/google-workspace
---
# HubSpot
*In this guide, you’ll learn how to activate the HubSpot integration in Prepr CMS.
This integration allows you to include HubSpot lists in your Prepr segments allowing you to render adaptive content to known HubSpot contacts.
By activating this integration, you also allow editors to include HubSpot forms in their content items.*
## Introduction
HubSpot is a CRM used to generate leads, close deals and improve web app visitor experiences. If HubSpot is your primary system to manage customers, visitor identification and/or forms, then you can integrate HubSpot lists and HubSpot forms to Prepr.
## Activating HubSpot integration
That’s it. The HubSpot integration is activated and you can create adaptive content for your HubSpot contacts and include HubSpot forms in your content items.
Source: https://docs.prepr.io/integrations/hubspot
---
# Jotform
*In this guide, you’ll learn how to activate the Jotform integration in Prepr CMS.
This integration allows marketers to embed interactive forms and collect responses directly in your content.*
## Introduction
Jotform is an online form builder that allows users to create and manage custom online forms, apps, and e-signatures.
If your tech stack includes Jotform to maintain your forms, you can add Jotform forms directly in Prepr.
## Activating Jotform integration
That’s it. The Jotform integration is activated and content editors can now embed Jotform forms in content items.
Source: https://docs.prepr.io/integrations/jotform
---
# Leadfeeder
*In this guide, you’ll learn how to activate the Leadfeeder integration in Prepr CMS. This integration provides you with the website visitor's industry and company size you can use to create segments for personalization.*
## Introduction
Leadfeeder is a comprehensive sales intelligence platform. It offers Leadfeeder’s website visitor tracking to identify which companies visit your website.
## Activating Leadfeeder integration to identify visitors
That’s it. The Leadfeeder integration is activated and you can create adaptive content for your B2B website visitors.
Source: https://docs.prepr.io/integrations/leadfeeder
---
# Leadinfo
*In this guide, you’ll learn how to activate the Leadinfo integration in Prepr CMS.
This integration provides you with the website visitor's industry and company size you can use to create segments for personalization.*
## Introduction
Leadinfo is business-to-business (B2B) software that unmasks anonymous website traffic by matching visitor IP addresses with public company databases.
## Activating Leadinfo integration to identify visitors
That’s it. The Leadinfo integration is activated and you can create adaptive content for your B2B website visitors.
Source: https://docs.prepr.io/integrations/leadinfo
---
# Mailchimp
*In this guide, you’ll learn how to activate the Mailchimp integration in Prepr CMS.
This integration allows marketers to embed interactive forms and collect responses directly in your content.*
## Introduction
Mailchimp is a comprehensive marketing platform that is not only an email marketing service but includes website building, social media marketing, and customer relationship management (CRM) tools.
If your tech stack includes Mailchimp to maintain your forms, you can embed Mailchimp signup forms directly in Prepr.
Source: https://docs.prepr.io/integrations/mailchimp
---
# Microsoft Entra ID (Azure)
*Integrate this cloud-based identity management solution to streamline your authentication permissions in your organization.*
## Introduction
Prepr offers several ways to log in: Using a passkey, single sign-on or by submitting an email address with a password.
Single sign-on (SSO) is a way to authenticate and log in to an application with just one set of credentials, rather than having to set up multiple usernames and passwords across different platforms.
It's a more secure process and prevents potentially losing or forgetting log-in credentials since it's stored through another service.
You can integrate with Microsoft Entra ID (Azure) if you want to let users sign-in through your company Microsoft Entra ID account.
{/* An additional agreement for Prepr is required to enable the Microsoft Entra ID (Azure) app. */}
Check out this [step-by-step guide](/project-setup/setting-up-sso#microsoft-entra-id) on how to set up SSO with Microsoft Entra ID.
Source: https://docs.prepr.io/integrations/azure
---
# OneLogin
*Integrate OneLogin to provide a single sign-on (SSO) solution to streamline your authentication permissions in your organization.*
## Introduction
Prepr offers several ways to log in: Using a passkey, single sign-on or by submitting an email address with a password.
Single sign-on (SSO) is a way to authenticate and log in to an application with just one set of credentials, rather than having to set up multiple usernames and passwords across different platforms.
It's a more secure process and prevents potentially losing or forgetting log-in credentials since it's stored through another service.
You can integrate with OneLogin using the SAML 2.0 open standard if you want to let users sign-in though your company identity and access management system.
Check out this [step-by-step guide](/project-setup/setting-up-sso#onelogin) on how to set up SSO with OneLogin.
Source: https://docs.prepr.io/integrations/onelogin
---
# OpenID Connect
*Integrate this secure authentication protocol built on top of OAuth 2.0 to streamline your authentication permissions in your organization.*
## Introduction
Prepr offers several ways to log in: Using a passkey, single sign-on or by submitting an email address with a password.
Single sign-on (SSO) is a way to authenticate and log in to an application with just one set of credentials, rather than having to set up multiple usernames and passwords across different platforms.
It's a more secure process and prevents potentially losing or forgetting log-in credentials since it's stored through another service.
You can integrate with an identity provider using the OpenID Connect open standard if you want to let users sign-in through your company IdP.
Check out this [step-by-step guide](/project-setup/setting-up-sso#openid-connect-oidc) on how to set up SSO with OpenID Connect.
Source: https://docs.prepr.io/integrations/openid
---
# Pipedrive
*In this guide, you’ll learn how to activate the Pipedrive integration in Prepr CMS.
This integration allows content editors to embed Pipedrive forms in their content items.*
## Introduction
Pipedrive is a sales CRM and pipeline management software.
## Activating Pipedrive integration
That’s it. The Pipedrive integration is activated and you can embed Pipedrive forms in content items.
Source: https://docs.prepr.io/integrations/pipedrive
---
# Prepr image processing
Prepr CMS can automatically process images on upload, enriching chosen text values in your image assets to help boost SEO.
You can activate this integration to either pull details from Exif data or to let AI generate alt text or other text values for images.
Source: https://docs.prepr.io/integrations/image-processing
---
# Propeller
*This integration allows content editors to select Propeller products in Prepr content items.*
## Introduction
You can connect Prepr to [Propeller](https://propeller-commerce.com/), an AI Platform for B2B Sales and Commerce.
This integration allows you to view and search Propeller products from within Prepr to include them in your web app content.
## Activating Propeller integration
That’s it. Now your web page includes catalog data from Propeller.
Prepr will synchronize your remote content automatically to keep it up to date.
Source: https://docs.prepr.io/integrations/propeller
---
# ProspectPro
*In this guide, you’ll learn how to activate the ProspectPro integration in Prepr CMS. This integration provides you with the website visitor's industry and company size you can use to create segments for personalization.*
## Introduction
ProspectPro is a B2B prospecting platform for finding and reaching high-value business leads using AI and data enrichment, especially in markets like the Netherlands.
## Activating ProspectPro integration to identify visitors
That’s it. The ProspectPro integration is activated and you can create adaptive content for your B2B website visitors.
Source: https://docs.prepr.io/integrations/prospectpro
---
# Salesforce
*This integration allows you to sync segments from a Salesforce account to Prepr.*
## Introduction
Salesforce includes CRM software that manages customer data, sales operations and marketing campaigns. If you use Salesforce as your primary customer data management platform, then you can integrate segments to Prepr.
Source: https://docs.prepr.io/integrations/salesforce
---
# SAML 2.0
*Integrate any identity provider (IdP) with this open standard to provide a cross-domain single sign-on (SSO) solution to streamline your authentication permissions in your organization.*
## Introduction
Prepr offers several ways to log in: Using a passkey, single sign-on or by submitting an email address with a password.
Single sign-on (SSO) is a way to authenticate and log in to an application with just one set of credentials, rather than having to set up multiple usernames and passwords across different platforms.
It's a more secure process and prevents potentially losing or forgetting log-in credentials since it's stored through another service.
You can integrate with an identity provider using the SAML 2.0 open standard if you want to let users sign-in though your company identity provider.
Check out this [step-by-step guide](/project-setup/setting-up-sso#saml-20) on how to set up SSO with a SAML 2.0 identity provider.
Source: https://docs.prepr.io/integrations/saml
---
# Shopify
*This integration allows content editors to select Shopify products in Prepr content items.*
## Introduction
You can connect Prepr to [Shopify](https://www.shopify.com/), a cloud-based commerce platform for creating and managing online stores.
This integration allows you to view and search the Shopify catalog from within Prepr and include products, product variants and collections in your web app content.
## Activating Shopify integration
That’s it. Now your web page includes catalog data from Shopify.
Prepr will synchronize your remote content automatically to keep it up to date.
Source: https://docs.prepr.io/integrations/shopify
---
# Snitcher
*In this guide, you’ll learn how to activate the Snitcher integration in Prepr CMS. This integration provides you with the website visitor's company profile data you can use to create segments for personalization.*
## Introduction
Snitcher is a B2B website visitor identification platform that identifies anonymous companies visiting a website by name and tracks their behavior.
You can integrate with Snitcher to create segments in Prepr based on the visitor's company profile data.
## Activating Snitcher integration to identify visitors
That’s it. The Snitcher integration is activated and you can create adaptive content for your B2B website visitors.
Source: https://docs.prepr.io/integrations/snitcher
---
# Twilio Segment
*This integration allows you to sync segments from a Twilio Segment account to Prepr.*
## Introduction
Segment is a customer data platform that captures and consolidates customer data into user profiles and audiences. If Segment is your primary customer data management system, then you can integrate your defined segments to Prepr.
Source: https://docs.prepr.io/integrations/segment
---
# Typeform
*Follow this guide to learn how to embed the Typeform templates in your web application.*
## Introduction
The Prepr integration with [Typeform](https://www.typeform.com/), an online form-building platform, lets you reference your form templates right inside Prepr. As a result, editors can easily search, preview, and add interactive web forms in content items.
You can make Typeform templates avaiable in content items by following the steps below.
## Activate Typeform integration for form content
That’s it. With this result, you have all the data to include the form from Typeform in your web application. In addition, Prepr will synchronize your remote content automatically to keep it up to date.
Source: https://docs.prepr.io/integrations/typeform
---
# Typesense
*This integration allows you to use the Typesense search engine for an optimized search experience on Prepr content in your web app.*
## Introduction
Prepr supports integration with Typesense, an open source, typo tolerant search engine. It enables your Prepr content items to be indexed using the Typesense algorithm, maximizing the search speed and content discovery in your web app.
## Integrating Typesense with Prepr content
Source: https://docs.prepr.io/integrations/typesense
---
# Vercel
*In this guide, you’ll learn how to set up the Vercel integration to deploy your website directly in Prepr CMS.*
## Introduction
Vercel is a cloud platform that automates front-end deployments for speed and efficiency.
When editors publish content changes in Prepr CMS, these changes appear in the live website.
However, [for statically deployed sites](/development/best-practices/csr-ssr-ssg#static-site-generation-ssg), content changes are only visible after a rebuild and deployment.
The Vercel integration mitigates this by allowing users to deploy to Vercel directly from Prepr, ensuring the website updates with the latest published content as needed.
## Enabling Vercel deployment in Prepr
That’s it. The Vercel integration is activated and any user can now build and deploy directly in Prepr.
Source: https://docs.prepr.io/integrations/vercel
---
# Zapier
*In this guide, you’ll learn how to set up a Zapier integration to send data from any listed external system to Prepr CMS.*
## Introduction
Zapier is an automation tool that connects apps and services, allowing users to create workflows (called *Zaps*) that trigger actions between them without coding.

We've set up two types of workflows to pair any app with Prepr.
- **Tag a Customer Profile** - Creates a tag on the matching Prepr visitor profile with a value from the external system.
- **Track event** - Records an event for a matching visitor in Prepr based on an action in the external system.
Before creating a *Zap*, you need to get the access token for the Prepr environment that Zapier needs to connect to.
## Getting the Prepr access token
Contact [Prepr Support](mailto:support@prepr.io) to get an access token for a Zapier integration to your Prepr environment.
Once you have the access token, you can create a *Zap*.
## Creating a *Zap*
To create a *Zap*, go to the [Prepr's Zapier integration page](https://zapier.com/apps/prepr/integrations).
### Tag a Customer Profile
This action is useful when you need to send some visitor data from another system to Prepr for segmentation.
For example, you can send the `industry` of a known visitor in Prepr when they request a demo through a HubSpot form.
Create a *Zap* to tag a visitor profile in Prepr with the following steps:
1. In the [Prepr's Zapier integration page](https://zapier.com/apps/prepr/integrations), choose a pairing app, for example, **HubSpot**.
2. Choose a trigger, for example, **New Form Submission**, choose **Tag a Customer Profile** for the action and click the **Connect these apps** button. Zapier creates the basic workflow (*Zap*) and you can customize it according to your needs.
3. Log in to your account for the app you paired and click the **Continue** button.
4. Depending on the trigger you chose, you'll need to choose some additional info, for example, a Demo request form.
5. Click the **Test trigger** button and choose any record for the test.
6. Click the **Sign in** button and paste the access token you copied from your Prepr environment.
7. In the **Tags** field, click the icon and choose the field you want to add. Enter the email address of the matching visitor in Prepr. Click the **Test step** button. You'll see a message about the tag being sent to Prepr.

8. In your Prepr environment, go to the **Segments** tab and open the visitor that you matched in the test.
You'll see a new tag for that visitor matching the field value in the paired app.
9. In Zapier, click the **Publish button** to activate the integration.
The integration to Prepr is now activated and Zapier automatically sends visitor data from the paired app to create a tag in Prepr for any matching visitor.
### Track Event
This action is useful when you need to trigger Prepr to create an event for segmentation.
For example, when you want to create the [*Subscribe* event](/data-collection/recording-events#subscribe) whenever a someone new subscribes to your Mailchimp newsletter.
1. In [Prepr's Zapier integration page](https://zapier.com/apps/prepr/integrations), choose a paired app, for example, **Mailchimp**.
2. Choose a trigger, for example, **New Subscriber**. Choose **Track Event** for the action and click the **Connect these apps** button. Zapier creates the basic workflow (*Zap*) and you can customize it according to your needs.
3. Sign in to your account for the paired app and click the **Continue** button.
4. Depending on the paired app, you need to choose additional info, for example, the Mailchimp **Audience**, and click the **Continue** button.
5. Click the **Test trigger** button and choose any record for the test.
6. Click the **Sign in** button and paste the access token you copied from your Prepr environment.
7. Enter a value for the [Event Name](/data-collection/recording-events), for example, *Subscribe*, and enter the email address or external ID to match the visitor in Prepr. Click the **Test step** button. You'll see a message about an event being sent to Prepr.

8. In your Prepr environment, go to the **Segments** tab and open the visitor that you matched in the test.
You'll see a new event recorded for that visitor.
9. In Zapier, click the **Publish button** to activate the integration.
The integration to Prepr is activated and Zapier triggers Prepr to create an event for every matching visitor in the paired app for the chosen trigger.
Source: https://docs.prepr.io/integrations/zapier
---
# Integrations
Extend Prepr CMS with one of the standard integrations listed below. If you need to build a custom integration, check out the [creating a custom remote source](/content-modeling/creating-a-custom-remote-source) or [using webhooks](/development/best-practices/webhooks) resources instead.
Source: https://docs.prepr.io/integrations
---
# Prepr Toolkit
*The Prepr Toolkit is a framework-agnostic TypeScript library that provides preview functionality, visual editing, and front-end setup for Prepr CMS personalization and A/B testing.
It's compatible with React, Next.js, Nuxt, Astro, and SvelteKit.*
Source: https://docs.prepr.io/prepr-toolkit
---
# Laravel GraphQL Provider for Prepr CMS
This Laravel package is a provider for the Prepr GraphQL API.
## Basics
- The SDK on [GitHub](https://github.com/preprio/laravel-graphql-sdk)
- Compatible with Laravel `v11x`.
## How to install
Install Package
```
composer require preprio/laravel-graphql-sdk
```
Added config in you're .env file and config/services.php
```php filename="config/services.php" copy
'prepr' => [
'endpoint' => env('PREPR_ENDPOINT')
]
```
```bash filename=".env" copy
PREPR_ENDPOINT={YOUR_API_ENDPOINT}
```
## Query the API
Option with query file (create file in app/Queries with .graphql extension):
```js copy
$response = Http::prepr([
'query' => 'name-of-the-file',
'variables' => [
'id' => 123,
]
]);
```
Option without a query file:
```js copy
$response = Http::prepr([
'raw-query' => 'query here',
'variables' => [
'id' => 123,
]
]);
```
Option with headers
```js copy
$response = Http::prepr([
'query' => 'name-of-the-file',
'variables' => [
'id' => 123
],
'headers' => [
'Prepr-Customer-Id' => request()->get('customer_id',request()->session()->getId())
]
]);
```
Source: https://docs.prepr.io/laravel/laravel-graphql-provider
---
# Laravel Rest Provider for Prepr CMS
This Laravel package is a provider for the Prepr REST API.
## Basics
- The SDK on [GitHub](https://github.com/preprio/laravel-rest-sdk)
- Compatible with Laravel `v9x`, `v10x`, `v11x`, `v12x`.
- Requires `GuzzleHttp 7.3.X`, and for version 3.0 and above PHP 8.x is required.
## Installation
You can install the Provider as a composer package.
For Laravel v10x, Laravel v11x and Laravel v12x
```bash copy
composer require preprio/laravel-rest-sdk:"^4.0"
```
For Laravel v9x
```bash copy
composer require preprio/laravel-rest-sdk:"^2.0"
```
### Publish config
Publish `prepr.php` config
```php filename="prep.php" copy
php artisan vendor:publish --provider="Preprio\PreprServiceProvider"
```
## Set up your .env file configuration
You can set the default configuration in your .env file of you Laravel project.
```bash filename=".env" copy
PREPR_URL=https://cdn.prepr.io/
PREPR_TOKEN=
```
## Laravel local caching
To make use of the caching feature of Laravel, add the following parameters to your .env file.
```bash filename=".env" copy
PREPR_CACHE=true
PREPR_CACHE_TIME=1800
```
## Making your first request
Let's start with getting all content items from your Prepr environment.
```php copy
path('publications')
->query([
'fields' => 'items'
])
->get();
if($apiRequest->getStatusCode() == 200) {
print_r($apiRequest->getResponse());
}
```
To get a single content item, pass the ID to the request.
```php copy
path('publications/{id}', [
'id' => '1236f0b1-b26d-4dde-b835-9e4e441a6d09'
])
->query([
'fields' => 'items'
])
->get();
if($apiRequest->getStatusCode() == 200) {
print_r($apiRequest->getResponse());
}
```
### Override the AccessToken in a request
The authorization can also be set for one specific request `->url('url')->authorization('token')`.
## Autopaging
```php copy
$apiRequest = (new Prepr)
->path('publications')
->query([
'limit' => 200 // optional
])
->autoPaging();
if($apiRequest->getStatusCode() == 200) {
dump($apiRequest->getResponse());
}
```
## Create, Update & Destroy
### Post
```php copy
$apiRequest = (new Prepr)
->path('publications')
->params([
'body' => 'Example'
])
->post();
if($apiRequest->getStatusCode() == 201) {
dump($apiRequest->getResponse());
}
```
### Put (Update)
```php copy
$apiRequest = (new Prepr)
->path('publications')
->params([
'body' => 'Example'
])
->put();
if($apiRequest->getStatusCode() == 200) {
dump($apiRequest->getResponse());
}
```
### Delete
```php copy
$apiRequest = (new Prepr)
->path('publications/{id}',[
'id' => 1
])
->delete();
if($apiRequest->getStatusCode() == 204) {
// Deleted.
}
```
### Multipart/Chunk upload
- Option 1
```php copy
use Illuminate\Support\Facades\Storage;
$source = Storage::readStream('image.jpg');
$apiRequest = (new Prepr)
->path('assets')
->params([
'body' => 'Example',
])
->file($source);
if($apiRequest->getStatusCode() == 200) {
dump($apiRequest->getResponse());
}
```
- Option 2
```php copy
use Illuminate\Support\Facades\Storage;
$source = Storage::get('image.jpg');
$apiRequest = (new Prepr)
->path('assets')
->params([
'body' => 'Example',
])
->file($source, 'image.jpg');
if($apiRequest->getStatusCode() == 200) {
dump($apiRequest->getResponse());
}
```
### Debug
For debug you can use `getRawResponse()`
Source: https://docs.prepr.io/laravel/laravel-rest-provider
---
# PHP + Prepr GraphQL SDK
This package is an SDK for the GraphQL API.
## Basics
The SDK on [GitHub](https://github.com/preprio/php-sdk)
Minimal PHP version: `^8.2`
Requires `GuzzleHttp ^7.7.0`
For Laravel projects we recommend using the Laravel providers for [REST](https://github.com/preprio/laravel-rest-sdk) or [GraphQL](https://github.com/preprio/laravel-graphql-sdk).
## Installation
You can install the SDK as a composer package.
```bash copy
composer require preprio/php-graphql-sdk
```
## Making your first request
Let's start with getting some content items from your Prepr environment.
```php copy
rawQuery('{
Posts( limit : 30 ) {
items {
_id
_slug
title
}
}
}')
->request();
print_r($apiRequest->getResponse());
```
In the example above, we wrote all of our arguments inside the query string. However, in most applications, the arguments to fields will be dynamic.
To add these properties, use the `variables` method.
```php copy
rawQuery('query ($search : String) {
Posts(where: { _search : $search }) {
items {
title
}
}
}')
->variables([
'search' => "amsterdam",
])
->request();
print_r($apiRequest->getResponse());
```
## Using query files
If you saved your GraphQL queries to a static file, you can use the following method to execute those:
```php copy
query('query_file.graphql')
->request();
print_r($apiRequest->getResponse());
```
## Adding headers
In some cases, you may need to add headers to your request.
For example, when using Prepr personalization with the `Prepr-Customer-Id` header.
The example below shows how to add extra headers to the requests.
```php copy
headers([
'Prepr-Customer-Id' => 'your-customers-session-or-customer-id'
])
->request();
print_r($apiRequest->getResponse());
```
## Debug Errors
With `$apiRequest->getRawResponse()` you can get the raw response from the Prepr API.
Source: https://docs.prepr.io/php/php-graphql-sdk
---
# PHP + Prepr REST SDK
This package is an SDK for the REST API.
## Basics
The SDK on [GitHub](https://github.com/preprio/php-sdk)
Minimal PHP version: `^8.2`
Requires `GuzzleHttp ^7.7.0`
For Laravel projects we recommend using the Laravel providers for [REST](https://github.com/preprio/laravel-rest-sdk) or [GraphQL](https://github.com/preprio/laravel-graphql-sdk).
## Installation
You can install the SDK as a composer package.
```bash copy
composer require preprio/php-rest-sdk
```
## Making your first request
Let's start with getting some content items from your Prepr environment.
```php copy
path('content_items')
->query([
'fields' => 'items'
])
->get();
if($apiRequest->getStatusCode() == 200) {
print_r($apiRequest->getResponse());
}
```
To get a single content item, pass the ID to the request.
```php copy
path('content_items/{id}', [
'id' => '1236f0b1-b26d-4dde-b835-9e4e441a6d09'
])
->query([
'fields' => 'items'
])
->get();
if($apiRequest->getStatusCode() == 200) {
print_r($apiRequest->getResponse());
}
```
### Auto paging results
To get all resources for an endpoint, you can use the auto paging feature.
```php copy
$apiRequest
->path('content_items')
->query([
'limit' => 200 // optional
])
->autoPaging();
if($apiRequest->getStatusCode() == 200) {
print_r($apiRequest->getResponse());
}
```
### Override the AccessToken in a request
The authorization can also be set for one specific request `->url('url')->authorization('token')`.
### Post
```php copy
$apiRequest
->path('content_items')
->params([
'body' => 'Example'
])
->post();
if($apiRequest->getStatusCode() == 201) {
print_r($apiRequest->getResponse());
}
```
### Put
```php copy
$apiRequest
->path('content_items')
->params([
'body' => 'Example'
])
->put();
if($apiRequest->getStatusCode() == 200) {
print_r($apiRequest->getResponse());
}
```
### Delete
```php copy
$apiRequest
->path('content_items/{id}',[
'id' => "398402d-dd-asd-ads3343dad"
])
->delete();
if($apiRequest->getStatusCode() == 204) {
// Deleted.
}
```
### Multipart/Chunk asset upload
```php copy
$apiRequest
->path('assets')
->params([
'body' => 'Example',
])
->file('/path/to/file.txt')
->post();
if($apiRequest->getStatusCode() == 200) {
print_r($apiRequest->getResponse());
}
```
### Debug Errors
With `$apiRequest->getRawResponse()` you can get the raw response from the Prepr API.
Source: https://docs.prepr.io/php/php-rest-sdk
---
# Getting started with the Prepr GraphQL API
*This guide shows you how to make your first request to fetch data from the Prepr GraphQL API in three easy steps.*
## Making your first API request
That’s it. You’ve made your first call to the Prepr GraphQL API.
## What's next?
You can [try out your GraphQL queries](/graphql-api/test-queries) using the *Apollo Explorer* tool.
To learn more about the Prepr GraphQL API, check out the following resources:
- [API basics](/graphql-api/api-basics)
- [Authorization](/graphql-api/authorization)
- [Caching](/graphql-api/caching)
Source: https://docs.prepr.io/graphql-api/get-started
---
# Testing your queries
*This article helps you test your GraphQL queries on actual Prepr content using the *API Explorer* tool.*
Prepr provides you with an interface based on the [*Apollo Explorer*](https://www.apollographql.com/tutorials/lift-off-part1/06-apollo-explorer) to test your requests before adding them to your web app. Within the API Explorer, you can write and validate your GraphQL queries, make a test run of the queries, and receive responses from the Prepr API straightaway.
You can open the API Explorer in two ways.
**From the Access token details page:**
1. Click the icon and choose the **Access tokens** option to view all access tokens. Click the desired access token to open its details.

2. On the **Access token details** page, click the **Open in API Explorer** link under the *API URL* field.
**From the Content item details page:**
1. Navigate to the **Content** tab and click the desired content item to open its details.

2. Click the icon and choose the **Open in API Explorer** option.
Whichever option you choose, you’ll be redirected to the API Explorer interface like in the image below.

Your API endpoint URL and schema will be registered in the API Explorer automatically, so you are ready to compose your query.

1. Use the **Operation** pane to compose a query.
2. On the left of the **Operation** pane, you will see the **Documentation** pane that shows all the fields and arguments available in your schema. Click the *plus icon* next to an element to add it to your query.
3. *(Optional)* In the lower part of the **Operation pane**, there are two additional tabs – **Variables** and [Headers](/graphql-api/api-basics#the-headers), where you can specify dynamic arguments and key-value pairs accordingly.
4. Once you’ve built your query, click the **Run** button.
5. Check the API response in the **Response** pane on the right. The response is available in both an interactive JSON and table format. You can also copy the response to your clipboard, download it as a CSV, or download the JSON.
6. If the query results meet your needs, copy the query to implement it in your front end easily.
Source: https://docs.prepr.io/graphql-api/test-queries
---
# API basics
*This article takes you through the basics of the Prepr GraphQL API.*
## The API URL
The GraphQL endpoint (*API URL*) looks like the URL below:
`https://graphql.prepr.io/`
When you add an environment, Prepr provides two API endpoints by default, *GraphQL Production* and *GraphQL Preview*. The *API URL* value for each of these is the same for any operation you perform.
Check out the [Authorization doc](/graphql-api/authorization) for more details.
## The HTTP method
In general, the *Hypertext Transfer Protocol (HTTP)* is how your application and the server communicate.
To call the GraphQL API, you will need to use the HTTP `POST` method with the `application/json` content type to process larger queries and to send the variables and an operation name along with the query.
### Using `GET` instead of `POST`
Keep the following points in mind when deciding on the HTTP method:
- Consider using the `GET` method only if `POST` doesn't work in your environment.
- While our GraphQL server supports `GET` for queries, it’s best to keep in mind that lengthy query strings can exceed URL length limits imposed by browsers and CDNs.
- `GET` can help with HTTP caching, but be cautious with complex operations.
## Headers
You can include some HTTP headers in the request, in other words, the key-value pairs to specify the request body format or additional information about the API request.
The GraphQL API supports the following list of HTTP headers:
||Value| Usage|
|-----|-----|----|
|`Prepr-Customer-Id`|The ID of a web app visitor. You can use the `__prepr_uid` cookie or an external reference ID from an identity provider to set this value.| The API uses this ID to determine which [adaptive content](/personalization/setting-up-personalization) or [A/B test variant](/ab-testing/setting-up-ab-testing) to return in the response.|
|`Prepr-Visitor-IP`|IP address of the web app visitor.|The API uses this value to determine the current IP-based Geolocation of the web app visitor. It's a more accurate value when the request is made server-side.|
|`Prepr-Context-utm_source`|The UTM source of the web app visitor.|Prepr uses this value to set the *UTM source tag* for the matching visitor profile. If no visitor profile exists yet, the API uses this value to determine which adaptive content to return in the response for a matching [UTM tag segment](/personalization/managing-segments#campaigns).|
|`Prepr-Context-utm_medium`|The UTM medium of the web app visitor.|Prepr uses this value to set the *UTM medium tag* for the matching visitor profile. If no visitor profile exists yet, the API uses this value to determine which adaptive content to return in the response for a matching [UTM tag segment](/personalization/managing-segments#campaigns).|
|`Prepr-Context-utm_term`|The UTM term of the web app visitor.|Prepr uses this value to set the *UTM term tag* for the matching visitor profile. If no visitor profile exists yet, the API uses this value to determine which adaptive content to return in the response for a matching [UTM tag segment](/personalization/managing-segments#campaigns).|
|`Prepr-Context-utm_content`|The UTM content of the web app visitor.|Prepr uses this value to set the *UTM content tag* for the matching visitor profile. If no visitor profile exists yet, the API uses this value to determine which adaptive content to return in the response for a matching [UTM tag segment](/personalization/managing-segments#campaigns).|
|`Prepr-Context-utm_campaign`|The UTM campaign of the web app visitor.|Prepr uses this value to set the *UTM campaign tag* for the matching visitor profile. If no visitor profile exists yet, the API uses this value to determine which adaptive content to return in the response for a matching [UTM tag segment](/personalization/managing-segments#campaigns).|
|`Prepr-Hubspot-Id`|HubSpot ID (cookie value) of a website visitor when you've set up the HubSpot pixel in your front end. |The API uses this ID to determine which adaptive content to return in the response for a matching [HubSpot segment](/personalization/managing-segments#hubspot).|
|`Prepr-Segments`| Used for [Visual Editing](/project-setup/setting-up-previews-and-visual-editing#enable-segment-and-ab-test-switches). The `_id` of a specific segment. You can use the `prepr_preview_segment` query parameter in the URL to set this value.| The API uses this value to determine which [adaptive content](/personalization/setting-up-personalization) to return in the response.|
|`Prepr-ABTesting`|Used for [Visual Editing](/project-setup/setting-up-previews-and-visual-editing#enable-segment-and-ab-test-switches). A value of `A` or `B`. You can use the `prepr_preview_ab` query parameter in the URL to set this value.| The API uses this value to determine which [A/B test variant](/ab-testing/setting-up-ab-testing) to return in the response.|
## Body
Include a query string with the following properties in the body of the request you send to the API:
- **query** — the full GraphQL query containing the operation type (currently, only the query type is supported), the types & fields requested, and any variables included.
- **operationName** — optional, but if included, must be present in the query.
- **variables** — optional if there are no variables included in the query.
See an example request below with the following variables:
```json copy
{
"postId": "c85aa36b-8796-4f0d-955e-b317f7f905a2",
"locale": "en-US"
}
```
The GraphQL API will validate and execute this query string and return a response in JSON format.
When a query contains a mistake, the API returns an error message in the response. Read more about possible [statuses and errors](/graphql-api/statuses-errors).
Source: https://docs.prepr.io/graphql-api/api-basics
---
# Authorization
*In this article, you’ll learn how to get access to the Prepr GraphQL API.*
## Access tokens
To query environment content using the GraphQL API, you need to have a valid access token. Prepr supports multiple access tokens with different [permissions](/graphql-api/authorization#permissions) per environment.
If you have a [shared schema](/project-setup/architecture-scenarios/shared-schema), you can also add an access token to query content items across all environments in your organization by going to the organization settings.

During the initial setup of an environment, Prepr automatically generates two unique access tokens — *GraphQL Production* and *GraphQL Preview*.

The access tokens are included in the GraphQL endpoint URLs as follows:
`https://graphql.prepr.io/`
You can find the *API URLs* in your Prepr environment by clicking the icon, choosing the **Access tokens** option and clicking the desired access token.

## Permissions
For each access token you create, you need to determine what kind of content it needs access to. Prepr uses *permissions* for that.
Permissions allow you to limit a token’s access to your environment content based on [content item statuses](/content-management/collaboration#workflow-status). For example, the default API endpoints have the following permissions:
- The *GraphQL Production* token allows you to retrieve all published content items.
- The *GraphQL Preview* token allows you to retrieve content items in all available statuses, including *To do*, *In progress*, *Review*, *Done*, and *Published*.
You can create a new GraphQL API access token anytime, for example, when adding a new front-end application or [upgrading to the latest API version](/graphql-api/upgrade-guide), and simply choose the permissions according to what's needed.

Source: https://docs.prepr.io/graphql-api/authorization
---
# Caching
*Caching helps applications perform faster and cost less. In this article, you'll learn about the Prepr API caching approach.*
All Prepr content is served by our globally distributed *content delivery network (CDN)*. When you send your first request to the API, its response is cached in an edge cache location of our partner [Fastly](https://fastly.com). If you repeat the same request, you get a cached response from the CDN within 12 ms (on average).
With Prepr’s CDN cache, you can deliver a great web performance and user experience, particularly:
- Reduce the load time of your page.
- Provide a more secure network.
- Ensure maximum availability and accessibility to your website.
Learn more about the Prepr API caching approach below.
## Two-level caching strategy
The Prepr API uses a multi-tiered caching approach that serves as real-time load-balancing between multiple CDNs.
Incoming requests are processed by the edge servers in locations closer to the end user. When those edge servers don't have a cached response for the query, they will retrieve information from our main cache layer in the Amsterdam Data Center before eventually retrieving it from Prepr's backend servers.
As a result, the two-level caching strategy significantly reduces the distance the information has to travel.
## Smart cache invalidation
Our CDN cache uses *Smart Invalidation*, which allows removing stale cache entries before their normal expiration time (also known as Time to live - TTL).
The *Smart Invalidation algorithm* continuously processes and analyzes all queries that pass through the CDN edge locations. Whenever [content items](/content-management/managing-content/managing-content-items) or [underlying schemas](/content-modeling/fundamentals#what-is-a-schema) change in Prepr, the edge servers automatically invalidate the associated data from the cache. This guarantees that the most current data is always served to your website visitors.
## No rate limits
No limits are enforced on requests that hit Prepr’s CDN cache. It means these requests don’t count toward the rate limits, and you can make unlimited cache hits.
## Caching headers
Prepr sets the following HTTP headers on all API responses by default:
| header attribute | description |
| ------------- |-------------|
| X-Prepr-Cache | Indicates whether the request was a HIT or a MISS. [Read more](https://developer.fastly.com/reference/http/http-headers/X-Cache/) |
| X-Prepr-Identity | Indicates the cache servers processing the response. [Read more](https://developer.fastly.com/reference/http/http-headers/X-Served-By/) |
| X-Prepr-Region | Indicates a region where the response is processed. |
## Purging cache
When you need a real-time sync of new content or you need to clear old versions of data in the case of a manual deployment, you can manually purge cache for an access token by doing the following:
1. Click the and choose the **Access tokens** option to view all the access tokens.
2. Click the access token for which you want to purge the cache and click the **Purge token cache** button.
3. Then click the **Purge cache** to confirm your decision.

## Read after write consistency
When you get a successful response for a mutation request, changes are persisted in Prepr. It is important to note that some changes are not visible immediately after the update. If you fetch content with a GraphQL API request right after a mutation with the REST API, you might get back stale content. Because, when you create, delete or update content the request is distributed around the globe with a short delay.
Source: https://docs.prepr.io/graphql-api/caching
---
# Statuses and errors
*This article describes the standard HTTP status and error codes the GraphQL API returns when resolving a query.*
## HTTP response status codes
The GraphQL API uses standard HTTP status codes to indicate whether a request is successful or not. In general, status codes within the *2xx range* indicate a successful request. When you receive an HTTP status code different from *2xx*, then you probably have one of the following issues:
- Client-side validation problems like an invalid query argument or access token (*4xx HTTP codes*).
- Server or connection problems (*5xx HTTP codes*).
| Status code | Description |
|-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **200 OK** | The request is successful. However, the API response might contain an error. For example, if your query is too complex or contains typos, etc. |
| **401 Unauthorized** | The access token is invalid. |
| **405 Method Not Allowed** | The requested HTTP method is not supported for the specified resource. |
| **412 Precondition failed** | There is an issue with the query, for example, there are non-UTF characters in the query or the actual query is too large. In the body of the response, you'll see something like `Query could not be compressed. Query limit is 27500 but length was 28196.` This means the actual query is too big and you might need to [compress](https://www.npmjs.com/package/graphql-query-compress) it. |
| **429 Too many requests** | You have exceeded the rate limit. |
| **5xx Internal Error or Service Unavailable** | Something went wrong on the Prepr server side. Try your request again after a few seconds. |
| **5x3 Service Unavailable** | In most cases this requests exceeds the edge rate limits. Try your request again after a few seconds. |
## Example GraphQL errors
The GraphQL API also verifies if a request is syntactically correct and mistake-free in the context of a given GraphQL schema. When a query contains a mistake, an error message can be returned in the response.
See some known GraphQL errors below.
**Example 1.** When a query size exceeds the 8kb limit, you’ll see the following error message:
```json copy
{
"errors": [
{
"message": "Syntax Error: Unexpected ",
"extensions": {
"category": "graphql"
},
"locations": [
{
"line": 1,
"column": 1
}
]
}
]
}
```
Prepr limits a query size to a maximum of 8 kb to increase data retrieval performance from the [cache](/graphql-api/caching). The limit cannot be adjusted at the moment. We recommend that you use fragments for large requests. Alternatively, some libraries will automatically remove unnecessary whitespace and comments, like [GraphQl Query Compress](https://www.npmjs.com/package/graphql-query-compress).
**Example 2.** When a wrong typecast is specified in a query (*Int/String/etc.*), the GraphQL API returns the following error message:
```json copy
{
"errors": [
{
"message": "Variable \"$id\" of type \"Int\" used in position expecting type \"String\".",
"extensions": {
"category": "graphql"
},
"locations": [
{
"line": 1,
"column": 21
}
]
}
]
}
```
Follow the tips in the error message to resolve an issue.
**Example 3.** When a request contains a field that is not available in the schema definition, the following error message will be returned:
```json copy
{
"errors": [
{
"message": "Cannot query field \"content_ref\" on type \"Blog\".",
"extensions": {
"category": "graphql"
},
"locations": [
{
"line": 5,
"column": 3
}
]
}
]
}
```
Check out your schema definition and update the requested field name to the correct one.
**Example 4.** When a typo is made in a property name, you’ll get the following error message:
```json copy
{
"errors": [
{
"message": "Cannot query field \"id\" on type \"Blog\". Did you mean \"_id\"?",
"extensions": {
"category": "graphql"
},
"locations": [
{
"line": 3,
"column": 8
}
]
}
]
}
```
Follow the tips in the error message to resolve an issue.
## Invisible unicode characters in the API response
If you see invisible unicode characters in the API response, it means the **Enable edit mode** is checked in the access token you're using.

These characters are Stega-encoded strings which produce invisible output to allow content editors to use the [Vercel **Edit Mode**](/project-setup/setting-up-previews-and-visual-editing#activate-edit-mode-in-your-front-end).
When you turn on edit mode in an access token, Stega-encoding serializes metadata into invisible UTF-8 encoded characters and appends them to string values.

Source: https://docs.prepr.io/graphql-api/statuses-errors
---
# GraphQL API diagnostic tools
*This article shows you how to use a couple of GraphQL API diagnostic tools to help solve some common issues.*
## Debugging a schema
If you're unable to see your schema in a schema introspection tool like *Apollo API Explorer* in Prepr or when making requests through Postman, it's useful to run an endpoint diagnosis to verify the schema is valid. In this case, you can debug the schema as follows:
1. Use an API diagnostic tool to debug the broken schema by executing the following command in the terminal:
```bash copy
npx diagnose-endpoint@1.1.0 --endpoint=""
```
2. Replace the placeholder value `` with the *API URL* of the access token from Prepr.
You know that the schema is well-formed when you get the message: `Could not find any problems with the endpoint.`
Otherwise, the tool displays an error message like one of the following:
- `⚠️ Invalid schema from introspection: Union type AllModels must define one or more member types.` -> This means your schema is completely empty.
- `Cannot query field "fieldName" on type "TypeName".`
- `Expected type "TypeName", found "InvalidType"`
- `Type "TypeName" was defined more than once.`
The detailed error message helps you pinpoint the entity or field that resulted in the corrupted schema.
## Checking the difference between two schemas
The [*GraphQL Schema Diff* tool](https://github.com/fabsrc/graphql-schema-diff) identifies differences between two GraphQL schemas, making it useful for the following cases:
- Before [syncing two schemas](/development/working-with-cicd/syncing-a-schema) across environments, it's a good idea to check for differences that could cause breaking changes before you start.
- Comparing two different API versions for the same environment, for example, when upgrading your API.
Run the `graphql-schema-diff` tool to view differences between two schemas or two API versions as follows:
1. Install the *GraphQL Schema Diff* tool by executing the following command in the terminal:
```bash copy
npm install -g graphql-schema-diff
```
2. Once installed, you can run the tool with the following command:
```bash copy
graphql-schema-diff -s
```
3. Replace the placeholder values `` and `` with the *API URL* of the access tokens for each schema you want to compare.
You'll get a similar result to the image below.

We trust that these diagnostic tools help you debug similar API-related issues more easily. Don't hesitate to reach out to [Prepr support](https://prepr.io/support) for questions or to add your own favorite diagnostic tools to this list.
Source: https://docs.prepr.io/graphql-api/diagnostic-tools
---
# Versioning & Upgrade guide
*Learn more about our version support policy and the steps you need to take to upgrade your app to a newer API version.*
When backwards-incompatible changes are made to the API, we release a new, dated version.
The current version is `2025-10-07`. For information on all API updates, view our upgrade guide below.
By default, requests made to the API use your access token's default API version (you can see that version in the Prepr UI)
unless you override it by setting the `Prepr-Version` header.
## Support policy
Prepr guarantees technical assistance and security updates for each new version of the GraphQL API for at least 24 months.
Please note that **we do not change your API version automatically to avoid breaking your code**. Once you are ready to upgrade, please follow the instructions below.
## How to upgrade to a newer GraphQL API version
Your API version controls the API behavior you see (for example, how your schema is generated and what fields you can request). When a major or breaking change is introduced to the API, Prepr releases a new version based on the release date. In this case, we encourage our customers to upgrade their GraphQL API versions as soon as possible.
To upgrade to a newer GraphQL API version, you need to generate a new access token for your Prepr environment as follows:
1. Click the icon and choose the **Access tokens** option to open the access tokens page.
2. Click the **Add access token** button and choose the **GraphQL API** option.

3. Enter the *Name* for the new access token. For example, the access token you use to retrieve all published content items can be titled *Published*. If you want to preview the unpublished items on your staging site, consider creating an additional *Preview* token.
4. Next, define permissions for this access token under the *GraphQL Permissions* section. Permissions allow you to choose which content item statuses are accessible for an access token. [Read more about GraphQL permissions](/graphql-api/authorization#permissions).
5. Choose the *Expiration date* and click **Save** to confirm the settings.
Once you've created a new access token, you'll notice the new API version indicated on the **Access tokens overview** page. By default, the new access token uses the latest GraphQL API version on the token generation date.
## Released GraphQL API versions
You can check out the previous Prepr GraphQL API releases below.
| version | release date | end-of-life on |
|-----------------|--------------|----------------|
| 2025-10-07 | 2025-10-07 | t.b.a |
| 2025-05-27 | 2025-05-27 | 2027-10-07 |
| 2024-12-05 | 2024-12-05 | 2027-05-27 |
| 2024-10-04 | 2024-10-04 | 2026-12-05 |
| 2024-06-12 | 2024-06-12 | 2026-10-04 |
| **END OF LIFE** |
| 2024-05-15 | 2024-05-15 | 2026-06-11 |
| 2024-03-26 | 2024-03-26 | 2026-05-15 |
| 2024-01-31 | 2024-01-31 | 2026-03-26 |
| 2023-11-02 | 2023-11-06 | 2026-01-31 |
| 2023-06-30 | 2023-06-30 | 2025-11-06 |
| 2023-04-17 | 2023-04-17 | 2025-06-30 |
| 2023-02-09 | 2023-02-09 | 2025-04-17 |
| 2023-01-10 | 2023-01-10 | 2025-01-10 |
| 2022-08-15 | 2022-08-15 | 2024-08-15 |
| 2022-03-20 | 2022-03-20 | 2024-03-20 |
| 2022-02-15 | 2022-02-15 | 2023-06-01 |
| 2021-11-29 | 2021-11-29 | 2022-11-29 |
| 2021-09-27 | 2021-09-27 | 2022-09-27 |
### Version `2025-10-07`
**What's new**
- `DefaultLocale` is added as a root query field, returning the environment's default locale.
- `_locale` is added to the Interface `Model`
- `_locales` is added to the Interface `Model`
**What's changed**
- The type of the `items` field of the ContentItems query has been changed from Union Type "AllModels" to non-null Interface type `Model`.
Previously, querying required inline fragments on `Model`, but now interface fields can be accessed directly for a cleaner query structure.
```graphql copy
# Before
query {
ContentItems {
items {
... on Model {
_id,
_created_on
}
}
}
}
# Since 2025-10-07
query {
ContentItems {
items {
# Interface fields can now be queried without the ... on Model prefix:
_id,
_created_on
}
}
}
```
- The `total` field of the ContentItems query is now marked as non-null.
- If in a query only the field `_localizations` is passed, the fields from inside that field will be set as the resolved fields for the query.
- Input validation for the field type `_DateTime` has changed and will now validate that microseconds (if set) are 0.
- All fields of type `Tag` are now marked as non-null.
- Added support for the `@oneOf` directive on input objects, to be applied in upcoming releases.
### Version `2025-05-27`
**What's new**
- Previously, you could add personalization and A/B test content only to a stack field directly included in the model. We've enhanced the *Stack* field to allow personalization and A/B test content at any level in the content structure. These include stack fields in components and within dynamic content fields.
- It's now possible to add a *Dynamic content* field to a component to add rich content sections to your section-based pages. For example, to publish a guide with your chosen styling.
- You can now access a default query to retrieve the locales available in your environment, making it easier for you to implement localization switches in your front end.
- We added support for the *Tags* field type to components.
**What's changed**
- We've changed the default sorting on `string` fields to be case-insensitive.
### Version `2024-12-05`
**What's new**
- Remote Source types now have a new default field `_json`. This field returns the raw data content retrieved from the remote source.
- Added a new Scalar Type `Json` to support the new field type on the Remote Source types.
- Remote Source fields can now be configured as a single-item field.
- Added `BlueskyPost` and `ThreadsPost` types for new supported embeds.
- Model Stack fields can now also be filtered on the Typename of a referenced component.
- You can now filter content items by the Model Asset field for a specific asset or if the Asset field is filled.
**What's changed**
- Model Stack fields filtered on a Content Item ID can now also filter if the component is referenced in a component (or sub-component) field.
### Version `2024-10-04`
**What's new**
- Stack & Reference fields can now be configured as a single-item field. This removes unnecessary arrays when using the field with a single element. Support for this feature has been added to all previous versions up to version `2023-02-09`.
**What's changed**
- A/B tests without a `B` variant will now return no element for B targeted visitors, instead of always defaulting to `A`.
### Version `2024-06-12`
**What's new**
- The Context Type is now extended with a new field `variant_key`. In the upcoming release of the Prepr UI, this key will make tracking conversions on A/B tests or Personalizations much easier.
- In preparation for the upcoming release of custom events, a new system field `_event` has been added to the schema. This field, which expects a name argument, return a count of events from the specified type.
- For existing Prepr environments ENUM types can now be set to legacy mode (returning as a STRING instead of a native ENUM type).
**What's changed**
- The specific event counter fields (`_comments`, `_purchases`, `_votes`, `_click-throughs`, `_shares`) have been removed from the default schema. Instead, you can use the new field `_event(name: Purchase)` to achieve the same result.
- If a locale is requested that is not set up in your Prepr environment, it will now throw a clear error.
- The `_Event` enum now also will hold all custom create event types.
- The input on filtering on Customer Relation has changed from argument `_type` to `event`.
- The Enum `CustomerRelationType` has been removed from the schema.
- Locales in the `_localizations` field are now consistently returned in alphabetical order (A-Z). This replaces the previous behavior where locales were returned in a unpredictable order.
```
// Before 2024-06-11
query {
ContentItems(
where: {
_typename_any : ["Post"]
_customer_relation : {
id : "ad5714b0-dea5-46b3-9386-0f109ebbe29b",
_type : BOOKMARKED
}
}
) { // }
}
```
```
// After 2024-06-12
query {
ContentItems(
where: {
_typename_any : ["Post"]
_customer_relation : {
id : "ad5714b0-dea5-46b3-9386-0f109ebbe29b",
event : Bookmark
}
}
) { // }
}
```
### Version `2024-05-15`
**What's new**
We are very excited to bring you the long-awaited feature to add fields to Assets. It is now possible to define your own fields to keep track of asset-specific info like
Copyright or Source in addition to the core asset fields for the title, description and the author. Now, it's also possible to enable localization for your assets. This means your editors can enter information about assets in the locale that is relevant for them.
**What's changed**
- If an API version is specified by using the HTTP header `Prepr-Version` and set to a non-existing version, this will now generate an exception.
### Version `2024-03-26`
**What's new**
We are thrilled to introduce the long-awaited Enumerations feature. We've broadened the schema to allow
you to create your own enumerations and use them in any model or component using the List field. The ability to reuse a single enumeration across multiple models
and components results in a cleaner schema.
**What's changed**
- All list fields are update to use the new Enumerations feature. The response type of all list fields is changed from String or String! to the matching enumeration type.
- All filters on list fields are updated from String to the matching enumeration type.
- Please review your migrated ENUM lists for entries that commence with a numerical character, as ENUM values do not permit starting with an integer.
### Version `2024-01-31`
**What's new**
- Stack fields are now available in components, making your schema's way more flexible.
- Filtering on content relations is now extended to Stack fields.
- We added new filters for all content reference, remote content and slug fields.
**What's changed**
- **Slug NULL filtering**\
If null is passed to the \_slug\_any filter, this release will return all content items without a set slug.
The behavior until this version was that all items were returned.
```graphql copy
query {
Posts(
where: {
_slug_any : null
}
)
}
```
- **Slug ANY filtering**\
If an empty list is passed to the \_slug\_any filter, this release will return all content items with a set slug.
The behavior until this version was that all items were returned.
```graphql copy
query {
Posts(
where: {
_slug_any : []
}
)
}
```
- **Content reference filtering**\
If an empty object is passed to any reference filter, this release will return all content items with at least one referenced item.
The behavior until this version was that all items were returned.
```graphql copy
query {
Posts(
where: {
sections: {}
}
)
}
```
### Version `2023-11-02`
**What's new**
- **Introducing Strict Mode**
*Strict Mode is released in public beta, if you have any feedback on this new feature, please let us know.*
Strict Mode means you get more accurate and reliable TypeScript types from your GraphQL schema.
When you enable Strict Mode, the resulting GraphQL type will be `String!`, for example, instead of `String`.
When the exclamation mark is omitted then it means the field is a string that will never be null.
In other words, code generators produce the TypeScript type of `string` instead of `string | null | undefined`.
That means you won't have to handle those additional cases in your front end, and TypeScript won't complain if you
write things like `author.name.toLowerCase()`.
**Some more**
- All model Types now include a new field `_last_published_on`. This field returns the timestamp of the last time the item was published.
- Union reference fields can now also be filtered based on the reference `ID`, `slug` of `typename`.
**What's changed**
- If you enable Strict Mode, all fields in your model and components that are marked as required are now marked as non-null.
- For all types (except Asset) the `_type` field has been removed, use `__typename` instead.
- Collection queries now strictly enforce a pagination limit of 100.
**Non-Null Markings**
- For the types FacebookPost, InstagramPost, SoundCloudPost, SpotifyPlaylist, TikTokPost, TwitterPost and VimeoPost the `url` field is now marked as non-null to match the ApplePodcast type.
- For all model types, the event counters are now marked as non-null Integer fields.
- For the types \_DateRange, \_DateTimeRange and BusinessHours all fields are now marked as non-null.
- The BusinessHoursPeriod type fields `open_day`, `open_time`, `close_day`, `close_time` and `is_closed` are now marked as non-null.
### Version `2023-06-30`
**What's new**
- In this release, we've added a new recommendation algorithm to the API. All models now have a [**People Also Viewed** recommendation query](/graphql-api/personalization-recommedations-people-also-viewed-content).
- An asset field can now be configured as a single-asset field. This removes unnecessary arrays when using an asset field with a single asset.
- The introduction of nested components has made content modeling in Prepr even more powerful. You can now [embed one component into another](/content-modeling/managing-components#create-a-nested-component), creating a parent-child relationship within a component.
- For complex filtering, we've added an `OR` filter to the collection filters.
- A new `not_contains` filter is available for all Text fields.
**What's changed**
- Internal links in the text will now remove the `` tags by default. If you need the plain text, change the field in your query from `body` to `text`.
New formatted response:
```graphql copy
query {
"data": {
"Page": {
"dynamic_field": [
{
"body": "Hi1 "
}
]
}
}
}
```
Response in version `2021-09-27`:
```graphql copy
query {
"data": {
"Recipe": {
"dynamic": [
{
"body": "Hi1"
},
{
"body": "H2"
}
]
}
}
}
```
Source: https://docs.prepr.io/graphql-api/upgrade-guide
---
# Introduction to the GraphQL API schema
*The GraphQL API uses a schema to describe which data types are available in your system and how you can query them. From this article, you’ll learn how the Prepr GraphQL API schema is generated and how you can use it to integrate content into your web app.*
## Schema generation
A schema is the definition of the content structure for your web app. In Prepr, a schema consists of several content types: *models*, *components*, and *remote sources*. Each of these content types comes with corresponding *fields*.
Prepr provides you with the GraphQL API you can use to integrate the content into your application. For each content type in Prepr, the GraphQL API creates a corresponding [GraphQL type](https://graphql.org/learn/schema/), including available fields. These types describe the set of possible data you can query from Prepr.
Each of the GraphQL types and GraphQL fields must have a unique name as follows:
- The *Type name* is in pascal-case with only alphanumeric characters.
- The *Field name* is in lower camel-case with alphanumeric characters and underscores only.
Some Type names are reserved for metadata or default types. You can check them out in [Reserved terms](/graphql-api/api-schema#reserved-terms).
The GraphQL API schema is generated automatically at request time, so it’s always current. We recommend [browsing a schema with an API Explorer](https://studio.apollographql.com/sandbox/explorer) to get a complete overview of GraphQL type definitions and filtering options applicable to your setup. Also, you can [inspect your schema using GraphQL Introspection](https://graphql.org/learn/introspection/).
Find more information about supported GraphQL types and fields by following the links below:
- [Models and components](/graphql-api/schema-models)
- [System fields](/graphql-api/schema-system-fields)
- [Field types](/graphql-api/schema-field-types)
## Querying a schema
Prepr provides an interface based on the Apollo Explorer to test your requests before integrating them into production. Within the API Explorer, you can write and validate your GraphQL queries, make a test run of the queries, and receive responses from the Prepr API straightaway. Find more information in [Draft your queries](/graphql-api/test-queries).
Once you run a query, it will be validated and executed against the GraphQL API schema. The GraphQL API uses standard HTTP status codes to indicate whether a request is successful. Check out [Statuses and errors](/graphql-api/statuses-errors) for more details.
## Reserved terms
The following *Type names* are default types and reserved for metadata:
- `Boolean`
- `BusinessHours`
- `Context`
- `Coordinates`
- `CoordinatesCircle`
- `DateTime`
- `Directive`
- `Enum`
- `EnumValueDefinition`
- `Field`
- `Float`
- `ID`
- `Int`
- `Interface`
- `KeyValue`
- `ListOf`
- `Location`
- `NonNull`
- `Number`
- `Object`
- `Paragraph`
- `Query`
- `Quote`
- `Resource`
- `Resolve`
- `Scalar`
- `Stack`
- `String`
- `Text`
- `TextFormat`
- `Type`
- `TypeWithFields`
- `Union`
- `UnresolvedField`
- `Asset`
- `AssetAlignment`
- `Assets`
- `CdnFile`
- `Channel`
- `Component`
- `ContentIntegration`
- `ContentIntegrations`
- `ContentItem`
- `ContentItems`
- `Customer`
- `CustomerScalar`
- `Model`
- `Publication`
- `Publications`
- `Story`
- `Tag`
- `User`
- `ApplePodcast`
- `BlueskyPost`
- `FacebookPost`
- `InstagramPost`
- `SoundCloudPost`
- `SpotifyPlaylist`
- `ThreadsPost`
- `TikTokPost`
- `TwitterPost`
- `VimeoPost`
- `YouTubePost`
- `ActiveCampaignEmbed`
- `HubSpotEmbed`
- `PipedriveEmbed`
- `TypeformEmbed`
- `Event`
- `NavigationItem`
- `embed`
- `embeds`
- `item`
- `items`
- `slug`
Including all types starting with `_` and ending with `_DESC` or `_ASC`.
Using one of the reserved terms as a field name will result in a schema collision error asking you to use a different name instead.
Source: https://docs.prepr.io/graphql-api/api-schema
---
# Strict Mode
Strict mode enforces stricter validation and adherence to the schema.
You can [enable strict mode](/development/best-practices/typescript#enable-strict-mode) for your schema on an access token.
If you enable strict mode on your access token only permissions with active required validation can be chosen from the permissions list.
Take note of [the workflow stage for the required validation](/development/best-practices/typescript#set-workflow-stages-to-trigger-the-required-validation).
In contrast to normal operation *Strict Mode* affects the associated GraphQL type.
For example all required text fields in your schema will
result in the GraphQL type `String!`, instead of `String`. When the exclamation mark is omitted then it means
the field is a string that will never be null.
In other words, code generators produce the TypeScript type of `string` instead of `string | null | undefined`.
This makes the front end code cleaner and more consistent.
Source: https://docs.prepr.io/graphql-api/strict-mode
---
# Models & Components
Prepr automatically generates the [GraphQL API schema](/graphql-api/api-schema) for your project based on models you create, including associated components, remote sources, and fields.
**Models** consist of a number of fields and components and determine the structure of the content in a web app. Each model has its own unique name used for interaction with the API as described in the following table. You can find a model name by navigating to **Schema → Model settings**.
|Prepr UI name | Description|
|--------------------------|--------------|
|**Singular name** |In pascal-case with only alphanumeric characters. For example, *NewsArticle*.|
|**Plural name**| In pascal-case with only alphanumeric characters. For example, *NewsArticles*. The Plural name can not be the same as the singular.
**Components** are predefined sets of fields that can be used in models or included in the *Stack field* but cannot be interacted with as an individual entry. Each component has a unique name used for interaction with the API, as the table below shows:
|Prepr UI name | Description|
|--------------------------|--------------|
|**Type name** |In pascal-case with only alphanumeric characters. For example, *SEOTags*.|
Next, check out [System fields](/graphql-api/schema-system-fields) and [Field types](/graphql-api/schema-field-types).
Source: https://docs.prepr.io/graphql-api/schema-models
---
# System fields
Some fields are system-generated. The system fields are read-only fields that provide information about the content or any related record from the system, like a Content item ID or when a content item was created or last changed. You can recognize system fields by the prefix `_`.
Please see the full list of system fields below.
## Content
### \_id
`UUID` Unique identifier for each item.
### \_\_typename
`string` At any point in a query `__typename` can be requested to get the name of the object type returned.
### \_environment\_id
`string` Unique identifier of the Prepr environment.
### \_created\_on
`ISO 8601` UTC time at which the content item was created.
### \_changed\_on
`ISO 8601` UTC time at which the content item was last updated.
### \_publish\_on
`ISO 8601` UTC time at which the content item is or will be published.
### \_last\_published\_on
`ISO 8601` UTC time when the content item was last published.
### \_slug
`string` Unique reference within a content model.
### \_read\_time
`int` If the content item contains String/Text fields, a calculated read time is available.
## Localization
When your Prepr environment is set-up for localization. The content model will be extended with the following fields:
### \_locale
`string` Locale that is returned for this content item.
### \_locales
`list of: string` List of locales that are present in the content item.
### \_localizations
`list of: self` Returns the content item in all available locales.
## Personalization and A/B testing
When A/B testing or personalization is enabled, the following fields are available for content items and components in the *Stack* to support options for analytics and SSR.
### \_context
`self` Grouping details about A/B testing or personalization with the following fields:
#### kind
`string` Contains a value of `PERSONALIZATION` or `AB_TEST`
#### group\_id
`string` Unique identifier for each personalization and A/B test group in the *Stack*
#### variant\_key
`string` Prepr tracking variant key to easily collect conversion data for A/B testing or personalization.
#### variant\_id
`string` The ID of the personalization or A/B test variant. For personalization the value is the segment, while for A/B testing, it will have a value of `A` OR `B`.
#### segments
`list of: string` Contains a list of segment ID's. Each segment will have two IDs listed, the segment *API ID* and the internal ID for the segment.
## Remote Source specific fields
### \_json
`json` This field returns the raw data content retrieved from the remote source.
## Events
The total count of events that are posted to Prepr.
**Note** Requesting fields listed below will reduce the cache time significantly (to aprox. 20 minutes).
### \_event(name: View)
`int` Total number of specified events for this content item.
### \_views
`int` Total number of View events for this content item.
### \_likes
`int` Total number of Like events for this content item.
### \_bookmarks
`int` Total number of Bookmark events for this content item.
### \_subscribes
`int` Total number of Subscribe events for this content item.
Source: https://docs.prepr.io/graphql-api/schema-system-fields
---
# Field types
The GraphQL schema is automatically generated based on models, components, remote sources, and fields. Prepr supports all GraphQL's default scalar types, such as *String, Int, Float, Boolean*, and some *custom types*. Each field type you add to a model has a unique name used for interaction with the API.
This page lists all core field types available for your API, as well as sample queries to retrieve content for these through the API. Also, it is recommended that you [browse a schema with an API Explorer](https://studio.apollographql.com/sandbox/explorer) to get a complete overview of all field type definitions and filtering options applicable to your setup.
## Text
*Text* fields return a simple string with the content of the field.
In the case of an HTML text field, the string can contain basic HTML tags for the paragraph, heading, bold, italic, underline, unordered list, ordered list, link, table and alignment styles.
### Resolving internal text links
The `href` value for a link to a content item is its *Slug*, for assets a download url to the file is provided.
To get the content item/asset ID instead, add the HTTP header `Prepr-Resolve-Internal-Links` to your request and set it to `false`.
The `href` value will now contain the content item ID prefixed with `puuid#`, assets ID's are prefixed with `auuid#`.
[Clear the cache on the *Access tokens* page](/graphql-api/caching#purging-cache) after adding this header to your request.
## List (Enumeration)
*List* fields return a simple string with the content of the field.
The return type matches the linked *Enumeration* like in the example below.

## Dynamic Content Field
The Dynamic Content Editor enables editors for a next-level authoring experience. Embed videos, social media posts, maps, assets, and components to create rich content items.
Check out the [Dynamic Content Field](/graphql-api/schema-field-types-dynamic-content-field) section on how to query all types of content.
## Assets
Assets are photos, videos, audio files or documents that you can attach to a content item. The Asset type comes with its own set of default fields. You can add fields to the [Asset model](/content-modeling/defining-the-asset-model) and include them in your request like in the example below.
**Type definition**
```graphql copy
type Asset {
_id: String
name: String
description: String
url: String
width: Int
height: Int
file_size: Int # the asset file size in bytes -> Available for assets uploaded since 23 December 2025
mime_type: String # for example "image/jpeg"
original_name: String
author: String
caption: String
alignment: EnumType AssetAlignment (left, center, right)
focal_point: _FocalPoint (y: Int, x: Int)
# Video-specific fields
duration: Int # Duration in seconds
playback_id: String
cover: String
# Additional example Asset model fields in the default locale
copyrighted: Boolean # Boolean field to mark an asset as copyrighted
author_name: String # Text field to store an author name
# Additional Asset model fields for other locales
# If you have asset data in multiple locales, include the
# _localizations param to get those additional fields
_localizations: {
# Example field to get values in different locales
copyrighted : Boolean
_locale: String
}
# Custom Enterprise only
cdn_files: Union type of CdnFileType
}
```
Check the sample query for retrieving the default asset fields like `_id`, `author`, `caption` and `alignment`.
### Images
The GraphQL API exposes a set of image transformation and formatting options. The following table lists the most common operations.
| Operation | Argument | Description |
|-----------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Resize** | `width`, `height` | To change the image size.*Options:* a value in pixels. If a focal point is set, the focal point values are returned in the response when resizing an image. | |
| **Crop** | `crop` | To crop a particular area of an image.*Options:* north, northeast, east, southeast, south, southwest, west, northwest, center, centre. |
| **Get focal point** | `focal point` | To retrieve the focal point (`x`, `y`) value. |
| **Presets** | `preset` | To retrieve cropped images generated in the Prepr Editor interface.*Options:* the name you defined for the preset you would like to retrieve. |
| **Formatting** | `format` | To define image format.*Options:* jpg, png, etc. Images are served in the best format if not specified. Read more in the Automatic image optimization paragraph below. |
| **Original File** | `as_file` | If true; returns the original uploaded file without any optimisation or compression. |
To perform image operations, you need to pass arguments to the image URL field as shown below:
#### Automatic image optimization
Images are automatically served in WebP or AVIF to browsers that natively support those image formats.
WebP and AVIF provide better image compression and faster page loading times than other file formats.
### Video
Video files uploaded to Prepr will be stored in and played from [Mux](https://www.mux.com/) for all new Prepr accounts.
You can configure streaming options for videos by including the `resolution` and `duration` attributes in your API request.
Using the `cover` attribute, you can [get a video thumbnail from Prepr](/content-management/managing-assets/managing-assets#replacing-video-thumbnails). Please see an example code snippet below.
To learn how to display video content in your web app, please follow our guides for [live video streaming](/development/best-practices/assets/live-video-stream) and [video files on-demand](/development/best-practices/assets/video-audio). For additional information, refer to the [Mux documentation](https://docs.mux.com/guides/play-your-videos).
### Audio
Audio files uploaded to Prepr will be stored in and played from [Mux](https://www.mux.com/) for all new Prepr accounts. You can configure streaming options for your audio files by including the `resolution` and `duration` attributes in the API request. Please see an example code snippet below.
To learn how to play audio content in your web app, please [follow our guidelines for audio files](/development/best-practices/assets/video-audio).
### Files
Files will be available by a simple url. The following table lists optional arguments for the url field.
| Argument | Description |
|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `inline` | If true; returns the file with the `Content-Disposition` header set to `inline`, this opens the file in the browser instead of the default download behaviour. |
### Fetching a single-asset field
You can choose between using a multiple-asset or single-asset field [when adding it to a model or component](/content-modeling/field-types#assets-field), depending on the number of assets you intend to add.
When querying a single-asset field, you'll receive a response similar to this:
```json copy
{
"data": {
"Post": {
"video": {
"playback_id" : "fyd7tJBz01bUEsbgs7d02US01K8Tp00SB3gWn01WPneVKeCmQIM",
"hls": "https://stream.mux.com/fyd7tJBz01bUEsbgs7d02US01K8Tp00SB3gWn01WPneVKeCmQIM.m3u8",
"duration": 12045
}
}
}
}
```
### Hostnames
To set up hostnames for streaming assets in your front-end application, for example, when using Nuxt/Next.JS's Image Optimization plugins or for CSP configurations), in your Prepr environment
click the icon and choose **Access Tokens** to get them in the *Image and file domains* section.

## Integer
Integers are whole number, and are often used to store stock quantities, prices in cents ect.
For example, here we have a product with an Integer field for quantity.
## Float
Float types are fields in which you can make number entries with decimal places, for accurate calculation such as the price of an item, distance, or weight.
For example, here we have a product with a Float field for price.
## Boolean
For example, here we have posts with a Boolean field for premium\_only.
## Stack
The Stack field is a powerful type that stores a collection of models and components useful for building web pages.
This powerful type includes any personalized or A/B test group details.
For more details on fetching A/B test content, check out the [GraphQL API A/B testing doc](/graphql-api/personalization-recommedations-ab-testing).\
For more details on fetching personalized content (Adaptive content), check out the [GraphQL API personalization doc](/graphql-api/personalization-recommendations-personalized-stack).
### One-to-many
Our Post model has a One-to-many stack to the Promo component. Fetching
the data is as simple as with any other model field.
### Union Type
In this example we have a Page model with a Stack that holds the model `Navigation` and the components
`Hero`, `Promo` and `Grid`. The example below shows how to fetch the different types.
### Fetching a single stack field
You can choose between using a [multiple or single stack field](/content-modeling/field-types#stack-field), depending on the number of content items or components you intend to add to the model or component.
When querying a single field, you'll receive a response similar to this:
```json copy
{
"data": {
"Page": {
"title" : "Home page",
"hero":
{
"__typename": "Hero",
"title": "All About Us ...",
}
}
}
}
```
## Content reference
The Content reference field stores a relation between one or more models. This powerful type allows you
for example to create relationships between Posts and a Category or Posts and Author models.
Prepr supports two types of Content references, One-to-many and Union Type references.
One-to-many references allow you to create relationships between two types of defined models. The Union Type fields
can reference to a set of models in a single field.
### One-to-many
Our Post model has a One-to-many content reference to the Category model. Fetching
the data is as simple as with any other model field.
### Union Type
In this example we have a Page model with a Union type content reference to the
`Hero`, `Promo` and `Grid` models. The example below shows how to fetch the different types.
### Fetching a single content reference field
You can choose between using a [multiple or single content reference field](/content-modeling/field-types#content-reference-field), depending on the number of referenced content items you intend to add to the model or component.
When querying a single field, you'll receive a response similar to this:
```json copy
{
"data": {
"Post": {
"title" : "Introducing Targeted Content personalization",
"category":
{
"name": "Deep Dive"
}
}
}
}
```
## Components
Components are transformed into their own GraphQL types. Components are often used to represent a set of reusable fields. To query the content of a component you need to specify the subfields like you do with a content model.
For example, we added a Component named `header` to the Page model.
## Remote content
The *Remote content* field allows an editor to reference content from an ecommerce platform, external CMS or
legacy system in Prepr content items. A Remote Source has its own fields which are configured in the schema.
For example our `ecommerce` source has a `product_id`, `category_id` and `image_url` field.
All Remote content items will come with a `_id` and `_json` field by default.
Check out the how to [set up remote content](/content-modeling/creating-a-custom-remote-source) to start with your first integration.
### Fetching a single remote content field
You can choose between using a [multiple or single remote content field](/content-modeling/field-types#content-reference-field), depending on the number of referenced items you intend to add to the model or component.
When querying a single field, you'll receive a response similar to this:
```json copy
{
"data": {
"Product": {
"ecommerce": {
"_id": "daa86b97-beec-488c-876d-aa48fb16720e",
"product_id": "V234792749-23487298347",
"category_id": "CEJKHDFKHGKFJH-33",
"image_url": "https://image.ecommerce.com/fyd7tJBz01bgUEsbs7d02US01K8Tp00SB3Wn01WPneVKeCmQIM.jpg"
}
}
}
}
```
#### Remote content before API version `2023-01-10` (deprecated)
In the API versions before `2023-01-10` Remote content fields are returned as generic `ContentIntegration` type.
Let's see the same example in the deprecated versions.
## Form
The *Form* field allows an editor to include forms from external sources in their content, such as from HubSpot, Typeform, Pipedrive, ActiveCampaign or Jotform.
Take a look at the example form request and response with their corresponding fields.
Check out the [HubSpot](/integrations/hubspot#make-hubspot-forms-available-in-content-items), [Typeform](/integrations/typeform#add-form-field-to-schema) and [Pipedrive](/integrations/pipedrive) integration docs on how to set these up to include forms in the schema.
## Date
The Date field adheres to ISO 8601 standard. For example, March 14, 2020 is represented as `2020-03-14`.
## Date & Time
The DateTime field adheres to ISO 8601 standard. For example, 09.30 AM on March 14, 2020 is represented as `2020-03-14T09:30:00+00:00`.
## Date & Time Ranges
The DateTime Range fields are following the specification of the Date / Date & Time fields and have two subfields, `from` and `until`.
## Location
## BusinessHours
Represents the time periods that this location is open for business. Holds a collection of BusinessHoursPeriod instances.
`regular_hours` Operating hours for the business.\
`special_hours` This typically includes holiday hours, and other times outside of regular operating hours.
These should override regular business hours,
## Color
The Color field is made up of HEX code.
## Tag
Tags in Prepr have a predefined schema. This means that the type
for any tag in the GraphQL schema follows the definition below.
Source: https://docs.prepr.io/graphql-api/schema-field-types
---
# Fetching single items
When you want to fetch an individual content item of a given type you can use the single item query type. As explained in the [schema generation docs](/graphql-api/api-schema), the name of the fields is the *Singular name* of the content model from which they derive.
In the following example, we use a content model called *Post*, with a *Singular name* of *Post*.
```graphql copy
query {
Post( id: "535c1e5a-4d52-4794-9136-71e28f2ce4c1" ) {
_id,
title
}
}
```
You can make your query more specific by including arguments. A full list of available arguments can be found below.
## Arguments
The following arguments are available or required when querying a single content item:
| argument | type | required | description |
| ------------- |-------------| -------------|----------------------------------------------------------------------|
| `id` | String | false | The ID of the content item you want to fetch |
| `slug` | String | false | The slug of the content item you want to fetch |
| `locale` | String | false | Locale for the content item. If not set, the default locale is used. |
| `environment_id` | String | false | The environment ID is required if an organization token is used. It is used to fetch the content item from a specific environment.|
## Querying by ID
Since IDs are unique in Prepr, you can find the content item you want using the id argument. Here's an example that shows how to query a Post type content item with the id "535c1e-...".
```graphql copy
query {
Post( id: "535c1e-4d52-4794-9136-71e28f2ce4c1" ) {
_id,
title
}
}
```
## Querying by Slug
Since slugs are unique within a content model, you can find a content item you want using the `slug` argument. Here's an example that shows how to query a Page type content item with the slug "about-us".
```graphql copy
query {
Page( slug: "about-us" ) {
_id,
_slug
title
}
}
```
## Querying single-item models
Some models only have a single content item. It's possible to query these items without providing an ID because only one item is returned. Here's an example that shows how to query such an item.
For more details on single-item models, check out the [Single-item model](/content-modeling/managing-models#single-item-model) docs.
Source: https://docs.prepr.io/graphql-api/fetching-single-items
---
# Fetching multiple items
When you want to fetch multiple content items of a given type you can use the *Plural name* of the content model for the query type, for example `Posts`.
Check out the [schema generation docs](/graphql-api/api-schema) for more details.
In the following example, we use a content model called *Post*, with a *Plural name* of *Posts*.
```graphql copy
query {
Posts( limit : 30 ) {
items {
_id
_slug
title
}
}
}
```
You can limit the number of content items returned by using the `limit` parameter. If you don't set this parameter, then 10 content items will be returned by default. To make your query more specific, consider including arguments from the following list.
## Arguments
The following arguments are available or required when querying multiple items:
| argument | type | required | description |
| ------------- |-------------| -------------| -------------|
| `locale` | String | false | Locale of a content item. If no locale is set, the default locale will be used. |
| `locales` | \[String] | false | Optional fallback locales. |
| `sort` | SortInputType | false | Read more in [Sorting](/graphql-api/fetching-sorting-collections). |
| `where` | WhereInputType | false | Read more in [Filtering](/graphql-api/fetching-filtering-collections). |
## Preview vs Production
Prepr provides default access tokens, *Preview* and *Production*, each with the necessary permissions to serve your staging and production sites, respectively. [Learn more about GraphQL permissions](/graphql-api/authorization#permissions).
Source: https://docs.prepr.io/graphql-api/fetching-collections
---
# Fetching multiple model items
When you want to retrieve content items from multiple models, you can use the `ContentItems` query.
Include the `__typename` argument to see which model the returned content items belong to. You can make your query more specific by including the necessary arguments for each model. Please see the example below.
```graphql copy
query {
ContentItems( limit : 30 ) {
items {
__typename
... on Post {
_id,
_slug
}
}
}
}
```
## Query only content items from specific models
To narrow query results to specific models, you can include a filter on its name by using the `_typename_any` argument.
The following query retrieves all content items from the *Post* and *Page* models:
```graphql copy
query {
ContentItems( where : { _typename_any : [ "Post", "Page" ] } ) {
items {
... on Post {
_id,
_slug
}
... on Page {
_id,
_slug
}
}
}
}
```
Additionally, you can also use multiple filters to retrieve only specific content items from a model. Check out the [Filtering options reference](/graphql-api/fetching-filtering-collections) for more details.
## Arguments
The following arguments are available or required when querying multiple items:
| argument | type | required | description |
|-------------------------|-----------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `locale` | String | false | Locale of a content item. If no locale is set, the default locale will be used. |
| `locales` | \[String!] | false | Optional fallback locales. |
| `sort` | SortInputType | false | Read more in the [Sorting options reference](/graphql-api/fetching-multi-type-collection#sorting-multi-type-collections). |
| `where` | WhereInputType | false | Read more in the [Filtering options reference](/graphql-api/fetching-multi-type-collection#filtering-multi-type-collections). |
| `_typename_any` | \[String] | false | Allows filtering content items based on the given types. |
| `people_also_viewed_id` | String | false | Allows recommending multi-type content based on the given IDs. Read more in [Fetching People Also Viewed content](/graphql-api/personalization-recommedations-people-also-viewed-content). |
## Filtering multi-model items
To filter multi-model items, include the `where` argument in your query, followed by the relevant filter types for the fields on your model. Check out the full list of the available filters below.
| filter | documentation |\
|----------------|-----------------------------------------------------------------------------------------------------------------------------|
| `ID` | [Read more](/graphql-api/fetching-filtering-collections#id) |
| `Search` | [Read more](/graphql-api/fetching-filtering-collections#full-text-search) |
| `Slug` | [Read more](/graphql-api/fetching-filtering-collections#slug) |
| `Tags` | [Read more](/graphql-api/fetching-filtering-collections#tags) |
| `Content Type` | [Read more](/graphql-api/fetching-multi-type-collection#query-content-items-that-match-a-list-of-given-type-names) |
## Query by `customer_relation`
You can track visitor engagement with your content items using [Prepr tracking](/data-collection/setting-up-the-tracking-code).
If visitors are allowed to bookmark, subscribe or like content items, the `customer_relation` filter allows you to fetch the content items based on the event, such as `Liked`.
[Read more about visitor content filtering](/graphql-api/fetching-filtering-collections#query-by-_customer_relation).
## Sorting multi-model items
When fetching multiple content items at once, you can define the order of returned records by using the `sort` argument. This can be particularly useful when working with large datasets or complex queries. It helps to quickly locate specific records in your project.
You can order results in ascending or descending order by specific system fields. More information can be found below.
### Sorting by common attributes
It's possible to sort by the common attributes `created`, `changed` and `published` dates in ascending or descending order. The current values for sorting those fields are `created_on_ASC`, `created_on_DESC`, `changed_on_ASC`,
`changed_on_DESC`, `publish_on_ASC`, `publish_on_DESC`.
```graphql copy
type Query {
ContentItems( sort : publish_on_DESC ) {
items {
_id,
title
}
}
}
```
### Sorting by popularity
This option will sort the content items on descending order by the number of views.
```graphql copy
type Query {
ContentItems( sort : popular ) {
items {
_id,
title
}
}
}
```
### Default ordering
If you don't pass an explicit order value the returned content items are ordered by `publish_on_DESC`. This means that the most recently published content item appear at the top of the list.
Source: https://docs.prepr.io/graphql-api/fetching-multi-type-collection
---
# Filtering when fetching multiple items
Prepr automatically generates filters for the field types you add to your content models. You can apply these filters when [fetching multiple content items](/graphql-api/fetching-collections). For that, include the `where` argument in your query, followed by the relevant filter types for the fields on your model. Check out the full list of the available filters below.
## ID
Every content item in Prepr has it's own unique identifier `_id` that can be queried using the following filters:
| argument | type | Behaviour |
| ------------- |----------| -------------|
| `_id_any` | \[String!] | Query content items that match any of the giving ids |
| `_id_nany` | \[String!] | Query content items that match none of the giving id's |
In this example we will query all the Posts that are listed in the `_id_any` argument.
```graphql copy
query {
Posts( where : { _id_any : [ "ff71321f-e2d6-4d4d-b62b-530d8fb92392", "ff71321f-e2d6-4d4d-b62b-530d8fb92392" ] } ) {
items {
_id,
title
}
}
}
```
## Slug
Prepr content items can be filtered by their slugs. For example to show a block like "More posts".
You can query the Slug field by using the `_slug_any` or `_slug_nany` argument.
| argument | type | Behaviour |\
| ------------ |----------|---------------------------------------------------------|
| `_slug_any` | \[String!] | Query content items that match any of the giving slugs |
| `_slug_any` | \[] | Query content items that have a slug |
| `_slug_any` | NULL | Query content items that don't have a slug |
| `_slug_nany` | \[String!] | Query content items that match none of the giving slugs |
In this example we query all the Posts with the exception of the ones that are listed in the `_slug_nany` argument.
```graphql copy
query {
Posts( where : { _slug_nany : ["/posts/olympics-results-2022"] } ) {
items {
_id
_slug
}
}
}
```
## Locales
You can specify `locale` as an argument when fetching a single item or multiple items.
If you don't specify a locale, the default locale of the environment (or organization) is used.
The locale argument cascades, meaning that all linked content items resolve with the locale as specified.
```graphql copy
query {
Posts( locale : "de-DE" ) {
items {
id,
title
}
}
}
```
Check the [Localization](/graphql-api/localization) page for more on this.
## String
String (Text) fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
|---------------------------|-------------|------------------------------------|
| `field_name` | String | Equals |
| `field_name_contains` | String | Included in part of the string |
| `field_name_not_contains` | String | Not included in part of the string |
| `field_name_starts_with` | String | Starts with string |
| `field_name_ends_with` | String | Ends with string |
| `field_name_any` | \[String!] | Matches is any of the giving strings equals the value |
For example let's filter all authors where the `name` field starts with `Mik`.
```graphql copy
query {
Authors( where : { name_starts_with : "Mik" } ) {
items {
_id
}
}
}
```
## Full-text search
You can use the `_search` argument to search for content items that contain a given text query.
You can include this argument to query content items for a specific model or across multiple models.
Check out the [fetching multiple model items reference](/graphql-api/fetching-multi-type-collection) for more info.
In the examples below, we query content items that mention "amsterdam" somewhere in a `string` field.
**Note** The full text search is not case-sensitive.
**Search options**
In combination with the `_search` argument, you can add the `_search_options` argument. The `SearchOptionInput` contains two boolean fields `includeReferences` and `includeNumeric` to extend the search to all numeric content fields or referenced content items.
```graphql copy
query {
Posts( where : { _search : "amsterdam", _search_options : { includeReferences : true, includeNumeric : true } }) {
items {
_id
}
}
}
```
## Integer
Integer fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
|------------------|----------|---------------------------|
| `field_name` | Integer | Equals |
| `field_name_gt` | Integer | Greater than |
| `field_name_lt` | Integer | Less than |
| `field_name_gte` | Integer | Greater than or equal to |
| `field_name_lte` | Integer | Less than or equal to |
For example let's filter all products where the `rating` field is `4` or higher.
```graphql copy
query {
Products( where : { rating_gt : 4 } ) {
items {
_id
}
}
}
```
## Float
Float fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
|------------------|--------|--------------------------|
| `field_name` | Float | Equals |
| `field_name_gt` | Float | Greater than |
| `field_name_lt` | Float | Less than |
| `field_name_gte` | Float | Greater than or equal to |
| `field_name_lte` | Float | Less than or equal to |
For example let's filter all products where the `price` field is `2.5` or higher.
```graphql copy
query {
Products( where : { price_gt : 2.5 } ) {
items {
_id
}
}
}
```
## Boolean
Boolean fields in your model can be filtered by using their field name.
| argument | type | Behaviour |
| ------------- |-------------| -------------|
| field\_name | Boolean | Equals |
For example let's filter all products where the `premium` field is `true`.
```graphql copy
query {
Products( where : { premium : true } ) {
items {
_id
}
}
}
```
## Assets
Asset fields in your model can be filtered by using the following filters:
| argument | type | Behaviour |
|----------------------|-----------|--------------------------------------------------------------------------------------|
| `field_name._id_any` | \[String!] | Query content items that have a reference to an asset matching any of the giving Ids |
| `field_name` | `{}` | Where the specified key has at least one asset is referenced |
For example let's filter all organizations that have a filled `image` field.
```graphql copy
query {
Organizations( where : { image : {} } ) {
items {
_id
}
}
}
```
## Simple Reference / Stack
All Content Reference or Stack fields that reference one model, can be filtered using filters on the fields of the model you are referencing.
| argument | type | Behaviour |
|--------------------|-----------|----------------------------------------------------------|
| `field_name.where` | WhereType | Where arguments for the referenced model |
| `field_name.where` | `{}` | Where the specified key has at least one item referenced |
| `field_name` | NULL | Where the specified field is empty |
For example let's filter all Products where there is at least a Variant in the `size` `small`.
```graphql copy
query {
Products( where : { variants : { size : "small" } } ) {
items {
_id
}
}
}
```
## Union Reference / Stack
All union Content Reference or Stack fields (reference fields that allow for multiple types) can be filtered using some specific filters.
| argument | type | Behaviour |
|----------------------------|-----------|-------------------------------------------------------------------------------------|
| `field_name._id_any` | \[String!] | Query content items that have a reference matching any of the giving ids |
| `field_name._id_all` | \[String!] | Query content items that have a reference matching all of the giving ids |
| `field_name._id_nany` | \[String!] | Query content items that have a reference matching none of the giving id's |
| `field_name._slug_any` | \[String!] | Query content items that have a reference matching any of the giving slugs |
| `field_name._slug_nany` | \[String!] | Query content items that have a reference matching none of the giving slugs |
| `field_name._typename_any` | \[String!] | Allows filtering content items that have a reference matching on of the given types |
| `field_name._slug_nany` | NULL | Where the specified field contains no references |
For Stack fields, the filters apply only to referenced content items. Since version 2024-12-05,
the `_typename_any` filter also applies to referenced component types.
An example let's filter all Product where the parent page is a category.
```graphql copy
query {
Products( where : { parent : { _typename_any : ["Category"] } } ) {
items {
_id
}
}
}
```
## Date
Date fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
| ------------- |-------------| -------------|
| `field_name` | String | Equals |
| `field_name_gt` | String | Greater than |
| `field_name_lt` | String | Less than |
| `field_name_gte` | String | Greater than or equal to |
| `field_name_lte` | String | Less than or equal to |
For example let's filter all events on 1 September 2022. Dates in Prepr are saved in UTC Strings (ISO 8601).
```graphql copy
query {
Events( where : { date : "2022-09-01" } ) {
items {
_id
}
}
}
```
## Date Range
Date range fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
|-------------------------|-------------| -------------|
| `field_name{from_gt}` | String | Greater than |
| `field_name{from_gte}` | String | Greater than or equal to |
| `field_name{from_lt}` | String | Less than |
| `field_name{from_lte}` | String | Less than or equal to |
| `field_name{until_gt}` | String | Greater than |
| `field_name{until_gte}` | String | Greater than or equal to |
| `field_name{until_lt}` | String | Less than |
| `field_name{until_lte}` | String | Less than or equal to |
For example let's filter all events after 28th August 2022. Dates in Prepr are saved in UTC Strings (ISO 8601).
```graphql copy
query {
Events( where : { "event_dates": { "from_gte": "2022-08-28" } } ) {
items {
_id
}
}
}
```
## DateTime
DateTime fields in your model and the system fields `_publish_on`, `_created_on` and `_changed_on` can be filtered by using following filters:
| argument | type | Behaviour |
| ------------- |-------------| -------------|
| `field_name` | String | Equals |
| `field_name_gt` | String | Greater than |
| `field_name_lt` | String | Less than |
| `field_name_gte` | String | Greater than or equal to |
| `field_name_lte` | String | Less than or equal to |
For example let's filter all events after 1 October 2022. Dates in Prepr are saved in UTC Strings (ISO 8601).
```graphql copy
query {
Events( where : { date_gt : "2022-10-01T08:00:00+00:00" } ) {
items {
_id
}
}
}
```
## DateTime Range
DateTime range fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
|-------------------------|-------------| -------------|
| `field_name{from_gt}` | String | Greater than |
| `field_name{from_gte}` | String | Greater than or equal to |
| `field_name{from_lt}` | String | Less than |
| `field_name{from_lte}` | String | Less than or equal to |
| `field_name{until_gt}` | String | Greater than |
| `field_name{until_gte}` | String | Greater than or equal to |
| `field_name{until_lt}` | String | Less than |
| `field_name{until_lte}` | String | Less than or equal to |
For example let's filter all events after 28th August 2022. Dates in Prepr are saved in UTC Strings (ISO 8601).
```graphql copy
query {
Events( where : { "event_dates": { "from_gte": "2022-08-20T00:00:00+00:00" } } ) {
items {
_id
}
}
}
```
## Remote Content
Remote content fields in your model can be filtered by the ID used in the Remote Source.
| argument | type | Behaviour |
|-----------------------|----------|-----------------------------------------------------|
| `field_name{_id_any}` | \[String] | Matches items referencing one of the specified ID's |
For example let's filter all Category pages where our product is mentioned.
```graphql copy
query {
ProductCategories( where : { products : {
_id_any : [
"2387hasdkury3287h376s-23483268"
]
}
}) {
items {
_id
title
}
}
}
```
## Business hours
Business hours fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
|----------------------------|--------------|--------------------------------|
| `field_name{open_day_in}` | \[DayOfWeek!] | Open on one of the given days |
| `field_name{open_on_in}` | \[\_Date!] | Open on one of the given dates |
For example let's filter all locations open on Monday.
```graphql copy
query {
Locations( where : { "business_hours": { "open_day_in": [ MONDAY, FRIDAY ] } } ) {
items {
_id
}
}
}
query {
Locations( where : { "business_hours": { "open_on_in": ["2025-02-10"] } } ) {
items {
_id
}
}
}
```
## List
List fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
| ------------- |-------------| -------------|
| `field_name` | String | Equals |
| `field_name_contains` | String | Included in part of the string |
| `field_name_any` | \[String!] | Equals one of the given strings |
| `field_name_starts_with` | String | Starts with string |
| `field_name_ends_with` | String | Ends with string |
For example let's filter all Products where the `color` field is `red` or `blue`.
```graphql copy
query {
Products( where : { color_any : ["red", "blue"] } ) {
items {
_id
}
}
}
```
## Location
Location fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
| ------------- |-------------| -------------|
| `field_name_within_circle` | Object | Object with a list of the following arguments |
| `field_name_within_circle.lat` | Float | - |
| `field_name_within_circle.lon` | Float | - |
| `field_name_within_circle.radius` | Int | Distance in kilometers |
For example let's filter all Events within 10 kilometers of `Amsterdam`.
```graphql copy
query {
Events( where : { location_within_circle : {
lat : 40.730610,
lon : -73.935242,
radius: 10
}
}) {
items {
_id
location {
latitude
longitude
}
}
}
}
```
## Tags
Tag fields in your model can be filtered by using following filters:
| argument | type | Behaviour |
| ------------- |-------------| -------------|
| `field_name_any` | \[String!] | Equals any of the given tags (or) |
| `field_name_all` | \[String!] | Equals all of the given tags (and) |
| `field_name_nany` | \[String!] | Equals none of the given tags |
| `field_name_has` | Boolean | Includes at least one tag |
The example below filters all Nobel prize winning authors with the `prize` tag and a value of `Nobel`.
```graphql copy
query {
Authors( where : { prize_any : "Nobel" } ) {
items {
_id
}
}
}
```
You can also filter by the slug of a tag instead of the actual value. The example below filters all Nobel prize winning authors with the `prize` tag and a slug of `nobel`.
```graphql copy
query {
Authors( where : { prize_any : ["slug:nobel"] } ) {
items {
_id
}
}
}
```
**Deprecated Tag Filters**
You can query tag fields using the `_tags_any`, `_tags_all`, `_tags_has`, `_tags_nany` arguments. These filters work on all tags referenced in a content item, not restricted by a field name.
| argument | type | Behaviour |
| ------------- |----------| -------------|
| `_tags_any` | \[String!] | Equals any of the given tags (or) |
| `_tags_all` | \[String!] | Equals all of the given tags (and) |
| `_tags_nany` | \[String!] | Includes at least one tag |
| `_tags_has` | Boolean | Equals none of the given tags |
## Query by `_customer_relation`
You can track visitor engagement with your content items using the [Prepr Tracking Code](/data-collection/setting-up-the-tracking-code).
If web app visitors are allowed to bookmark, subscribe or like content items, the `_customer_relation` filter allows you to fetch content items based on the event, such as `Liked`.
You can use this filter option when [fetching multi-model items](/graphql-api/fetching-multi-type-collection).
For example, you can query all *Post* items bookmarked by the specified visitor, using the `_customer_relation` filter option.
```graphql copy
query {
ContentItems(
where: {
_typename_any : ["Post"]
_customer_relation : {
id : "ad5714b0-dea5-46b3-9386-0f109ebbe29b",
event : Bookmark
}
}
) {
items {
... on Post {
name
}
}
}
}
```
Instead of the visitor ID, `id`, you can also query using the visitor's *Reference ID*, `reference_id`.
```graphql copy
query {
ContentItems(
where: {
_typename_any : ["Post"]
_customer_relation : {
reference_id : "ad5714b0-dea5-46b3-9386-0f109ebbe29b",
event : Bookmark
}
}
) {
items {
... on Post {
name
}
}
}
}
```
New Event types created in the tracking API are available after a few hours.
## Environment ID filter
If you are using the GraphQL API with a multi-environment token you can filter content items on the environment they are created in.
| argument | type | Behaviour |\
|-----------------------|----------|-----------|
| `_environment_id_any` | \[String!] | Qeury content items from environments with the specified IDs |
In this example we query all *Posts* that are created in any of the environments listed in the `__environment_id_any` argument.
```graphql copy
query {
Posts( where : { _environment_id_any : [ "ff71321f-e2d6-4d4d-b62b-530d8fb92392", "ff71321f-e2d6-4d4d-b62b-530d8fb92392" ] } ) {
items {
_id,
_environment_id
title
}
}
}
```
## Combining filters
It's also possible to combine different filters. This example shows how to do that.
```graphql copy
query {
Products( where : {
variants : { size : "small" }
premium: true,
rating_gt: 4
} ) {
items {
_id
}
}
}
```
## Conditional filters
Conditional filters allow you to filter results based on sets of criteria. In this example, we filter all items that have a boolean field set to true or have a high rating:
```graphql copy
query {
Products( where : { _or : [ { boost: true }, { rating_gt: 4.5 } ] } )
{
items {
_id
}
}
}
```
Source: https://docs.prepr.io/graphql-api/fetching-filtering-collections
---
# Sorting when fetching multiple items
When [fetching multiple content items](/graphql-api/fetching-collections), you can define the order of returned records by using the `sort` argument. This can be particularly useful when working with large datasets or complex queries. It helps to quickly locate specific records in your project.
You can order results in ascending or descending order by specific system fields and some non-relational custom fields that you add to your model. More information can be found below.
## Sorting by common attributes
It's possible to sort by the common attributes `created`, `changed` and `published` dates in ascending or descending order. The current values for sorting those fields are `created_on_ASC`, `created_on_DESC`, `changed_on_ASC`,
`changed_on_DESC`, `publish_on_ASC`, `publish_on_DESC`.
```graphql copy
query {
Posts( sort : publish_on_DESC, locale : "en-US") {
items {
_id,
title
}
}
}
```
## Sorting by fields
You can sort content items by the content of all `Text`, `Float`, `Integer` and `Date` `DateTime` fields in either ascending or descending order. For example we've added an integer field `order` to the model. That creates two new sort options: `order_ASC` and `order_DESC`.
```graphql copy
query {
Posts( sort : order_ASC, locale : "en-US") {
items {
_id,
title
}
}
}
```
## Default ordering
If you don't pass an explicit order value the returned content items are ordered by `publish_on_DESC`. This means that the most recently published content item appear at the top of the list.
Source: https://docs.prepr.io/graphql-api/fetching-sorting-collections
---
# Paginating when fetching multiple items
Pagination allows you to display a large number of records in a more manageable way. To enable pagination, you need to define two parameters in your query: `Limit` and `Skip`. By defining these parameters, you can break down a large dataset into smaller parts that can be displayed on separate pages. Please find more details below.
```graphql copy
query {
Posts ( limit : 15, skip: 0 ) {
items {
_id,
title
},
total
}
}
```
In the above example, a client retrieves content items by repeating the same request, changing the `skip` query parameter as follows:
`Page 1: skip=0, limit=15`
`Page 2: skip=15, limit=15`
`Page 3: skip=30, limit=15`
`etc.`
## Limit
The `Limit` parameter sets a maximum number of results to return per query. It breaks down the entire results into portions. By default, API returns 10 items per call.
## Skip
The `Skip` parameter sets an offset, i.e., how many items to omit before returning the results.
## Returned data
When fetching multiple items, the response includes the meta-data field `total` and the requested items in the items field. The total field contains the total number of content items in that collection.
Please note, adding the total field requires processing costs, and in large datasets this can slow down the query significantly.
Source: https://docs.prepr.io/graphql-api/fetching-paginating-collections
---
# Localizing content
Prepr offers the Localization API that enables you to publish content in multiple locales. You can [add locales to your project](/content-management/localizing-content#adding-a-locale) by navigating to the environment or organization settings.

When fetching [multiple items](/graphql-api/fetching-collections) and [a single item](/graphql-api/fetching-single-items), you can specify a `locale` as an argument in your query. If no locale is specified, the *default locale* is used.
Please note, the `locales` argument cascades, meaning that all linked content items will resolve with the specified locale.
[Check out our localization docs](/content-management/localizing-content) for more details.
## Available locales
Retrieve the locales available in your environment, making it easy to implement localization switches in your front end.
The `_DefaultLocale` will return the environment's default locale.
```graphql copy
query {
_Locales
_DefaultLocale
}
```
## Fallback locales
When fetching multiple items, `locales` argument can be given to set fallback locale(s). The argument is processed from left to right.
In this example, if there is no version for the `de-DE` locale, the `en-US` locale will be returned.
```graphql copy
query {
Pages ( locales : ["de-DE", "en-US"] ) {
items {
_id,
title
}
}
}
```
## Fetching all locales
All content items are generated with a `_localizations` field that contains all available locales for that content item. Make sure to add the same fields to the top level of the query, since the `_localizations` field will only return the values of items that are also specified outside the `_localizations` field.
```graphql copy
query {
Pages {
items {
_id,
_slug
title
_localizations {
_id
_slug
title
}
}
}
}
```
## Fetching locale by HTTP header
You can add the `Prepr-Locale` header to your request to override the default locale.
```html copy
'Prepr-Locale' : 'en-US'
```
Source: https://docs.prepr.io/graphql-api/localization
---
# Previewing content
Accessing unpublished content can be useful for previewing how a new content item will look before making it available to everybody. The GraphQL API gives you control over whether you want to access published or unpublished content items.
To view content items that are not yet published, use the *Preview* access token created by Prepr during the initial environment setup.

With this access token, you can retrieve content items in all available statuses, including *To do*, *In progress*, *Review*, *Done*, and *Published*, and make any necessary changes before publishing them.

Alternatively, you can create a new access token yourself and define token permissions according to your specific needs. [Read more about GraphQL permissions](/graphql-api/authorization#permissions).
Source: https://docs.prepr.io/graphql-api/fetching-previewing-content
---
# Fetching A/B tests with Prepr GraphQL
{/* A/B testing */}
# Fetching an A/B test
A/B test your content, check out the [A/B testing guide](/ab-testing/setting-up-ab-testing) for a quick introduction.
## How to fetch a variant
An A/B test variant is set up for any item or component in a *Stack*. Prepr automatically links an A or B variant to a particular visitor when they visit the web app. To show one of the variants, simply pass the *Prepr-Customer-Id* in the header of the GraphQL API request.
If you set up your content item, for example, a *Page* with *Stack* elements and you activate an A/B test on a specific element in the stack, for example, the *Page header*, the query will return the appropriate version for that visitor. Use the [*\_context* system field ](/graphql-api/schema-system-fields#_context) to set up reporting variables in your front-end.
## Pre-fetching the variant
A/B test your content by pre-fetching both variants for static site rendering.
If you enabled A/B testing on your content model, pass the `personalize: false` argument in the API request. Use the [\_context system field](/graphql-api/schema-system-fields#_context) to choose the variant that you want to show in your front-end.
Source: https://docs.prepr.io/graphql-api/personalization-recommedations-ab-testing
---
# Fetching personalized content with Prepr GraphQL API
{/*
PERSONALIZATION */}
# Fetching personalized content
Prepr lets you generate fully customized experiences to boost engagement and conversion rates in a web app. [Check out the Personalization guide](/personalization/setting-up-personalization) for more details.
A native personalization engine is built into the [Stack field](/content-modeling/field-types#stack-field).
Once added to a model, the Stack field may include multiple content items and components and allows editors to personalize these elements with *Adaptive content*.
With *Adaptive content*, content editors can make different versions of content for various visitor segments.
You can then show the right content to each visitor based on their segment giving them a personalized experience.
## Segmentation
You can link adaptive content variants to one or more segments in Prepr.
- When visitors are maintained in Prepr, you can [create segments with conditions](/personalization/managing-segments) directly in Prepr to target them with adaptive content based on their behavior on the web app.
The front end can then [fetch the adaptive content by visitor](#fetch-content-by-visitor) in this case.
- If you source segments from another application, for example, by using an external CRM/CDP system, then you need to create a matching [segment](/personalization/managing-segments#use-external-segments-from-crmcdp-systems) in Prepr to reference the external segment.
This segment can then be used to determine the adaptive content for these visitors. The front end can then [fetch adaptive content by variant](#fetch-content-by-variant) or [fetch it by segment](#fetch-content-by-segment) in this case.
## How to fetch adaptive content
Fetch adaptive content by a visitor, a variant or a segment. We recommend fetching personalizations by the `Prepr-Customer-ID` header.
### Fetch content by visitor
If visitors are maintained in Prepr, you can retrieve adaptive content using an API request that includes an HTTP header called `Prepr-Customer-Id` containing the ID of the web app visitor.
This allows Prepr to match a web app visitor to a segment in your Prepr environment and return the matching content for this visitor based on the personalized content in the *Stack* field.
Note, the visitor ID in the HTTP header must correspond to the ID used when [sending events to Prepr](/data-collection).
If the `Prepr-Customer-Id` header is not sent or contains no data, the API will return the variants set up on the *Stack* field and marked for *All other users*, in other words, that are not matched to any segment.
See an example query below:
If the API request includes an HTTP header `Prepr-Customer-Id` containing an ID for the visitor, then you’ll get adaptive content shown on the **RESPONSE** tab. Otherwise, general content will be returned like in the **ALL OTHER USERS** tab.
#### HTTP-headers for first page load personalization
When a user visits your page for the first time, a profile has not been created yet.
To help segment the customer into a segment you can add HTTP headers to the request.
If you set up the *Prepr Toolkit*, you request the headers from the toolkit directly and pass it along with your query.
| HTTP HEADER | Value |
|----------------------------|--------------------------|
| Prepr-Customer-ID | ID for this visitor |
| Prepr-Visitor-IP | The visitors IP address |
| Prepr-Context-UTM\_campaign | The query UTM campaign |
| Prepr-Context-UTM\_source | The query UTM source |
| Prepr-Context-UTM\_medium | The query UTM medium |
| Prepr-Context-UTM\_term | The query UTM term |
| Prepr-Context-UTM\_content | The query UTM content |
| Prepr-Hubspot-Id | The HubSpot cookie value |
### Fetch content by variant
A variant is the adaptive content linked to one or more segments.
You can retrieve adaptive content for a specific personalized variant by passing the segments as arguments in your query. For example, use a segment that is from an external CDP/CRM system.
See an example query below:
### Fetch content by segment
If visitors are created and maintained in another CRM or CDP system, you can pass the segments as a comma-separated string using the HTTP Header `Prepr-Segments`. Include the segment *API ID* in your API request.
See an example query below:
If the API request includes an HTTP header `Prepr-Segments` containing the segment IDs, then you’ll get adaptive content shown on the **RESPONSE** tab. Otherwise, general content will be returned as the **ALL OTHER USERS** tab shows.
## How to pre-fetch adaptive content for static (SSG) websites
Prepr lets you pre-fetch all data for all segments so you can implement personalization for static (SSG) websites.
For that, pass the `personalize: false` argument in the API request. Use the [\_context system field](/graphql-api/schema-system-fields#_context) to choose the variant that you want to show in your front-end.
See an example query below:
Source: https://docs.prepr.io/graphql-api/personalization-recommendations-personalized-stack
---
# Fetching similar content
Display content similar to a given item based on common content attributes and content. Check out the [Recommendations guide](/recommendations) for a quick introduction.
The GraphQL API creates a corresponding GraphQL Type with a *"similarity"* algorithm for each content model in your environment. The plural Type name of the content model is prefixed with `Similar_`. For example Post generates a Type `Similar_Posts`. Pagination options (except limit) and the `total` items field are disabled when using this query.
```graphql copy
query {
Similar_Posts(
id : "b3f76d7d-732b-4fd6-a9e9-a967dba3d09b" ) {
items {
_id,
title
}
}
}
```
## Filtering recommended content items
To narrow down the recommended content items, use the same filters that are available when [filtering collections](/graphql-api/fetching-filtering-collections).
In the following example, we query all Posts where `premium` is set to true.
```graphql copy
query {
Similar_Posts(
id : "b3f76d7d-732b-4fd6-a9e9-a967dba3d09b", where : { premium : true } ) {
items {
_id
}
}
}
```
## Optimizing the recommendation algorithm
The recommendation algorithm for similar items finds items based on the following criteria:
|Criteria|Default weight|Description|
|--------|----|------|
|Topics |1.4| Highest weight. Topics are extracted automatically by the AI text analysis engine. It identifies specific people, places, or concepts within the item text and finds items with matching topics.|
|Content References|1.2| Matches content items linked to the main item through any content references whether in the *Stack*, *Component* or *Dynamic content* field. For example, articles in the same category and articles by the same author.|
|Tags|0.8| Matches items based on tag values in the main content item.|
You can tweak the default logic to align with your own business priorities.
For example, if your content items are related more by their common tags rather than linked items, you can increase the importance of *Tags*.
Here’s an example of how that works:
```graphql copy
query {
Similar_Articles(
id: "0cbe2455-124c-4820-b2ac-dcc4261e150c"
where: {
_publish_on_gt: "2021-01-01T00:00:00+00:00"
categories: {
_slug_any: [
"ux-design", "development"
]
}
},
limit: 3
rules: {
entities: 0
tags: 1
references: 0.5
}
) {
items {
_id
title
}
}
}
```
- We added rules: **`{ entities: 0, tags: 1, references: 0.5 }`** to indicate that topics should not be included and that the tag criteria has higher priority than references.
We recommend starting with the default setting and adding rules only if the result does not meet your expectations.
Source: https://docs.prepr.io/graphql-api/personalization-recommedations-similar-content
---
# Fetching 'People Also Viewed' recommendations
{/* recommendations */}
# Fetching People Also Viewed content
Display content similar to a given item based on visitors' behavior. Check out the [Recommendations guide](/recommendations) for a quick introduction.
The GraphQL API creates a corresponding GraphQL Type with a "people also viewed" algorithm for each content model in your environment. The plural Type name of the content model is prefixed with `PeopleAlsoViewed_`. For example Post generates a Type `PeopleAlsoViewed_Posts`.
```graphql copy
query {
PeopleAlsoViewed_Posts(
id : "90276002-d628-4ba6-b3c8-f756c486b67b" ) {
items {
_id,
title
}
}
}
```
## Filtering recommended content items
To narrow down the recommended content items, use the same filters that are available when [filtering collections](/graphql-api/fetching-filtering-collections).
In the following example, we query all Posts where `premium` is set to true.
```graphql copy
query {
PeopleAlsoViewed_Posts(
id : "90276002-d628-4ba6-b3c8-f756c486b67b", where : { premium : true } ) {
items {
_id
}
}
}
```
Source: https://docs.prepr.io/graphql-api/personalization-recommedations-people-also-viewed-content
---
# Fetching popular content with Prepr GraphQL API
{/* recommendations */}
# Fetching popular content
Display your trending content items. Check out the [Recommendations guide](/recommendations) for a quick introduction.
The GraphQL API creates a corresponding GraphQL Type with a *"Popularity"* algorithm for each content model in your environment. The plural Type name of the content model is prefixed with *Popular\_*. For example Post generates a Type `Popular_Posts`.
```graphql copy
query {
Popular_Posts {
items {
_id,
title
}
}
}
```
Check the [Capture Documentation](/data-collection) on how to get started with events.
## Filtering recommended content items
To narrow down the recommended content items, use the same filters that are available when [filtering collections](/graphql-api/fetching-filtering-collections).
In the following example, we query all Posts where `premium` is set to true.
```graphql copy
query {
Popular_Posts(
where : { premium : true } ) {
items {
_id
}
}
}
```
Source: https://docs.prepr.io/graphql-api/personalization-recommedations-popular-content
---
# Dynamic content field
*The Dynamic Content field* allows content editors to combine various elements such as headings, texts, videos, social media posts, maps, assets, remote content, and components to create rich content.
This page lists sample requests you can use to query different types of content available in the Dynamic Content field.
## Dynamic content components
### Heading (text)
Heading elements contain two fields. `body` contains the content of the heading, `format` tells you if the heading is H1, H2, H3 ect. formatted.
### Paragraph (text)
Paragraph elements contain two fields. `body` contains the content of the paragraph, `format` tells you if the heading is HTML or plain formatted.
### Links (text)
You can identify links with the ` ` HTML tag in the `body` and `html` string values like in the example response below.
### Assets
You can get the `url` of the asset and in the case of videos, the `playback_id`.
Check out the [Asset Field](/graphql-api/schema-field-types#assets) section for all asset fields.
### Components
Components are transformed into their own GraphQL types. Components are often used to represent a set of reusable fields. To query the content from a component you need to specify the subfields, like you do for any other type.
In our example the `dynamic_content_field` contains a component named `HeaderComponent`.
### Social posts
Social posts can be one of the following types: InstagramPost, YouTubePost, FacebookPost, TwitterPost, SpotifyPlaylist, TikTokPost, VimeoPost, SoundCloudPost, ThreadsPost and BlueskyPost.
The returned urls are [oEmbed](https://oembed.com/) supported urls.
### Quote
### Menu item (NavigationItem)
### Location (Coordinates)
### Remote content
The *Remote content* field allows an editor to reference content from an ecommerce platform, external CMS or
legacy system in Prepr content items. A Remote Source has its own fields which are configured in the schema.
For example our `ecommerce` source has a `product_id`, `category_id` and `image_url` field.
If you add a Remote source to the Dynamic Content Editor a specific Remote Source Collection is generated
#### Remote content before API version `2023-01-10` (deprecated)
In the API versions before `2023-01-10` Remote content fields are returned as generic `ContentIntegrations` and `ContentIntegration` type.
Let's see the same example in the deprecated versions.
Source: https://docs.prepr.io/graphql-api/schema-field-types-dynamic-content-field
---
# GraphQL API
The Prepr GraphQL API is a read-only API based on the GraphQL language.
The GraphQL API offers more precise and flexible queries than the REST API. You can precisely define the data you want and get this data with only a single call instead of multiple REST requests.
All Prepr environments have a GraphQL schema generated from associated content models. The schema is generated dynamically at request time. This ensures that changes to the schema design are instantly reflected in your web application.
If you need any assistance, you're welcome to reach out to us:
[Join our Slack community](https://slack.prepr.io) or
[Reach out to our support team](https://prepr.io/support).
Source: https://docs.prepr.io/graphql-api
---
# Get started with the Prepr MCP server
*Connect your preferred AI client to the Prepr MCP server, then test the connection with your Prepr content.*
## Before you start
All clients connect to the same remote MCP server:
- *Server URL:* `https://mcp.prepr.io`
- **Authentication:** OAuth, when supported by the client
## Choose your AI client
Review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions) and [typical use cases](/prepr-mcp-server/use-cases) before you start building workflows.
Source: https://docs.prepr.io/prepr-mcp-server/getting-started
---
# Authorization
*In this article, you’ll learn how to access the Prepr MCP server.*
## Prepr MCP server URL
You need to specify the Prepr MCP server URL to connect to any agent.
`https://mcp.prepr.io`
## Connecting with OAuth
OAuth is a secure, industry-standard protocol that allows you to authorize one app or service to sign in to another.
When you connect your agent to the Prer MCP server, you are automatically redirected to log into your environment.
With this protocol, your existing Prepr [user role permissions](/project-setup/managing-roles-and-permissions#action-based-permissions) apply when making AI requests.
For example, if the **Delete** permission is disabled for your user role, the agent cannot delete content items.

## Connecting with MCP token
If your preferred AI client does not support OAuth for remote MCP connections, then you need to request an MCP server token to connect to the Prepr MCP server.
To request an MCP token, contact [Prepr Support](mailto:support@prepr.io?subject=Request%20Prepr%20MCP%20token) and specify the Prepr environment you need access to.
When connecting your AI client to the Prepr MCP server, in addition to providing the URL, you need to add an Authorization header:
- Prepr MCP server URL: `https://mcp.prepr.io`
- Authorization header: `Authorization: Bearer YOUR_ACCESS_TOKEN`

## What's next?
To see which MCP tools are available and what actions they support, check the [Available tools and actions](/prepr-mcp-server/available-tools-and-actions) guide.
Source: https://docs.prepr.io/prepr-mcp-server/authorization
---
# Available tools and actions
Depending on your [user role permissions](/prepr-mcp-server/authorization#connecting-with-oauth), the Prepr MCP server allows an AI client to perform actions using the tools listed below.
## Context and discovery
The AI client can inspect environment settings, locale configurations, and content models to safely map out your data layout.
- `get_initial_context` - Bootstrap the session with environment metadata, locales, workflow stages, current user, and a schema summary.
- `get_schema` - Inspect the full environment schema and resolve exact model IDs and field IDs.
- `list_locales` - See which locales exist in the current environment before reading or changing localized content.
- `list_workflow_stages` - Discover the valid workflow stages you can move content through.
- `list_users` - Find assignable users before assigning content items.
- `get_current_user` - Resolve the currently authenticated user, especially for requests like "assign to me".
## Read and query
The AI client can locate, filter, and fetch your existing content and assets without altering any database records.
- `query_items` - Search and filter content items when you need one or more matching candidates.
- `query_assets` - Search and filter assets such as images, videos, audio, documents, and PDFs.
- `get_item` - Fetch a single content item when you already know its exact Content Item ID.
## Write and mutation
If granted write access via your token, the AI client can actively build, modify, transition, or publish items inside Prepr.
- `create_item` - Create one localized content item in a specific model.
- `update_item` - Fully update one localized content item while keeping the existing locale payload shape intact.
- `patch_items` - Patch supported root-level fields on an existing localized content item without sending a full update payload.
- `upload_asset` - Import a public image, audio file, or document URL into Prepr as an asset.
- `publish_items` - Publish one or more localized content items now or schedule them for later.
- `unpublish_items` - Unpublish one or more localized content items in a specific locale.
- `change_workflow_stage_items` - Move one or more content items to a different workflow stage.
## Collaboration and deletion
The AI handles content assignments, editorial comments, and explicit-confirmation removals to keep teamwork seamless and safe.
- `assign_items` - Assign one or more localized content items to a specific user.
- `unassign_items` - Remove the assigned user from one or more localized content items.
- `add_comment` - Add an editorial comment to a content field on a localized content item.
- `delete_items` - Permanently delete an entire content item across all locales, with explicit confirmation.
- `delete_item_locale` - Permanently delete one locale from a content item, with explicit confirmation.
- `give_feedback` - Report MCP tool issues such as broken behavior, missing capabilities, confusing output, or unclear documentation.
Source: https://docs.prepr.io/prepr-mcp-server/available-tools-and-actions
---
# Release notes
*Find new features and important updates to the Prepr MCP server in these release notes.*
## Added schema settings exposed to the LLM
*July 21st, 2026*
This week we've added new instructions and expanded the schema settings the MCP server exposes to the LLM.
- Added new instructions on element box.
- Schema now exposes default values to the LLM.
- Schema now exposes min/max values of fields to the LLM.
- Schema now exposes enum values of fields.
- Fixed issue, so that MCP server no longer creates deleted components.
## Expanded media support and fixed filtering
*July 21st, 2026*
This week we've expanded the media support.
- You can upload all asset types (images/document/audio/video) with a file size up to 10GB. Each uploaded file by default gets an S3 presigned URL.
- Added support for social media embed fields on content models.
- Fixed filtering issues: filtering on a slug by regex and filtering items that are marked as *Needs attention*.
## Expanded field support and multi-locale handling
*June 26th, 2026*
This week we expanded editing support for more Prepr field types and improved update behavior across locales.
- When publishing a item that fails validation, the MCP server now returns the validation errors
- Fixed multi-locale item handling when updating content, it now returns the newly created or updated locale
- Added support for all social posts, DateTime, Coordinates and BusinessHours fields on model and component
## Dynamic Content Editor fields now supports custom components
*June 22nd, 2026*
The Dynamic Content Editor fields now supports custom components + adding and removing or updating specific fields.
## Single and multiple content references and assets
*June 22nd, 2026*
We updated the instruction for content references and asset fields to make single and multiple input the same.
## Increased response limit
*June 22nd, 2026*
Response limit upped to 2 MB (previous 500kb) to support large items
## Improved query for items with ENUM list
*June 22nd, 2026*
To improve querying items, sort is now updated to an ENUM list of options so the model knows where to choose from.
## Improved component handling
*June 17th, 2026*
- Components now support sub-components when editing content
- Dynamic content fields in components now can be used like stack fields
## Error message for stale sessions
*June 17th, 2026*
We've updated the error message for stale sessions to indicate when you need to restart the client.
## Updated query limit
*June 16th, 2026*
The query items tool has been updated to offer a limit up to 50 items per request (instead of 10), and a new `has_more` property is returned.
## Feedback tool available
*June 16th, 2026*
The Prepr MCP server now exposes a feedback tool for the model to submit feedback.
## Agent access mirrors user role permissions
*June 15th, 2026*
The AI client access to content matches your Prepr user role permissions when making requests. For example, the agent cannot delete content items if your user role **Delete** permission is disabled in Prepr.
## Prepr MCP server domain is updated
*June 15th, 2026*
The MCP server domain is changed from `https://mcp.prepr.io/mcp` to `https://mcp.prepr.io` (the `/mcp` path is no longer required and is marked as deprecated).
## Use Prepr URLs directly in your agent
*June 15th, 2026*
You can now simply paste the Prepr URL of a specific content item or asset in your agent instead of finding and copying an ID for your agent to identify specific content items or assets.
## OAuth support is now available
*June 12th, 2026*
The Prepr MCP server now supports OAuth.
This means it's easier for you to give access to your AI client access to the Prepr MCP server by simply logging into your Prepr environment.
Source: https://docs.prepr.io/prepr-mcp-server/release-notes
---
# Use cases
*Use these examples to explore practical workflows for the Prepr MCP server.*
### Create a content item from an external content source
Use this workflow when your team drafts blog posts, landing pages, or announcements in an external source like Google Drive before publishing them in Prepr.
1. Make sure your AI client can access the source document through a shared link, a [connected document source](https://support.claude.com/en/collections/17879307-pre-built-connectors), or a local export.
2. Tell the AI which Prepr content model and locale to use.
3. Ask it to copy only the relevant fields and define which status to put it in and who to assign it to.
Example prompt:
```text copy
Find the draft article document, Top 20 AI trends in 2026,
in my Google Drive, and create a new Post item in Prepr
for locale en-GB. Copy the title, intro, and body content,
set the workflow stage to Review, and assign it to Mike.
If a required field is missing, ask me before creating the item.
```
### Create demo content based on your Prepr schema
Use this workflow when you want to quickly populate a new environment with realistic sample content based on the content models in your Prepr schema.
1. Tell the AI which content models should be populated first.
2. Specify the locale, number of demo items, and the tone or subject matter you want to use.
3. Ask it to inspect the schema so it can generate values that match the required fields and field types.
4. Review the created items and refine them before using them in demos, previews, or QA.
Example prompt:
```text copy
Review my Prepr schema and create demo content for the Post,
Author, and Category models in locale en-US. Create 10
realistic blog posts with matching author profiles and categories.
Use complete values for required fields and keep the content
suitable for a product demo.
```
### Audit unpublished content
Use this workflow when you want to review content quality before publishing.
1. Ask the AI to check for missing fields, metadata, and asset references.
2. Review the results and decide which items should be updated first.
Example prompt:
```text copy
Audit my unpublished blog posts. List any drafts that are missing
a featured image, SEO title, or author profile.
```
### Update existing content from review feedback
Use this workflow when feedback is stored in a document and needs to be applied to an existing content item.
1. Make sure your AI client can access the feedback document through a shared link, a [connected document source](https://support.claude.com/en/collections/17879307-pre-built-connectors), or a local export.
2. Specify the content model, locale, and the fields that may be changed.
3. Ask it to keep important identifiers such as the slug unchanged unless you explicitly request otherwise.
Example prompt:
```text copy
Review the feedback in this document and update the matching Post
item in Prepr for locale en-US. Keep the existing slug, update the
body copy and summary, and move the item to Ready for review.
```
### Make recommendations to merge similar articles
Use this workflow when your content library contains overlapping articles and you want help identifying which items should be merged, consolidated, or retired.
1. Ask the AI to compare articles on similar topics across a specific model or section of your content library.
2. Tell it to highlight duplicate themes, overlapping titles, repeated keywords, and outdated or thin content.
3. Ask it to recommend which article should remain the primary item and which ones should be merged, redirected, or archived.
Example prompt:
```text copy
Review all Post items in my Help Center category and identify
articles that cover very similar topics. Recommend which items
should be merged, which one should remain as the main article,
and which ones should be archived or redirected.
```
### Replace outdated branding information based on a branding style guide
Use this workflow when your company updates product names, tone of voice, terminology, or messaging guidelines and you want to apply them consistently across your content.
1. Make sure your AI client can access the branding style guide through a shared link, a [connected document source](https://support.claude.com/en/collections/17879307-pre-built-connectors), or a local export.
2. Tell the AI which content models or sections should be reviewed first.
3. Ask it to find outdated brand terms, inconsistent naming, and messaging that no longer matches the current style guide.
4. Review the proposed changes before applying them across multiple content items.
Example prompt:
```text copy
Review the branding style guide in this document and compare it
with all LandingPage and Post items in locale en-US. Identify
outdated product names, old taglines, and inconsistent terminology,
then suggest the updates needed to align the content with the new
brand guidelines.
```
### Prepare localized content
Use this workflow when you manage more than one locale and want help reviewing or comparing existing localized entries.
1. Identify the source item and the target locale.
2. Tell the AI whether it should create a new localized item or update an existing one.
3. Ask it to flag any fields that need manual translation or editorial review.
Example prompt:
```text copy
Review the Dutch version (locale nl-NL) of the existing
LandingPage item based on the English source. Check for
inconsistencies between the language variants and suggest
alternative translations.
```
### Translate content items and update them
Use this workflow when you want to translate content in bulk from a source locale and write the translations directly back into Prepr.
1. Tell the AI which content model, source locale, and target locale to use.
2. Ask it to list the available locales first to confirm the correct locale codes.
3. Ask it to show you the items it plans to translate before making any changes.
4. Review a sample of the translations before approving the full batch.
Example prompt:
```text copy
List all published Post items in locale en-US that don't yet have
a locale entry for nl-NL. Translate the title, intro, and body
fields into Dutch, create the nl-NL locale entry for each item,
and set the workflow stage to In review. Show me the first five
translations before processing the full batch.
```
### Update workflow state across a batch of items
Use this workflow when content operations need to be coordinated across many entries at once.
1. Define a clear filter such as a model, tag, locale, or workflow state.
2. Ask the AI to show the affected items before applying changes.
3. Apply assignment or workflow changes only after confirming the batch looks correct.
Example prompt:
```text copy
Find all Article items tagged spring-campaign in locale en-US,
assign them to Sarah, move them to In review, and show me the list
of affected items before applying the changes.
```
### Bulk-create content items for a migration
Use this workflow when content is being migrated from another CMS, a spreadsheet export, or a folder of structured documents.
1. Put the migration source files, mappings, and any transformation notes in a location your AI client can access.
2. Tell the AI which content model and locale to target.
3. Ask it to report missing required fields and mapping problems instead of silently skipping records.
4. Create drafts first, then validate the batch before publishing anything.
Example prompt:
```text copy
Using the migration data in this folder, create draft Post items
in Prepr for locale en-US. Map each source file to one content item,
preserve the publish date when available, and report any rows that
are missing required fields instead of skipping them silently.
```
### Validate migration output before publishing
Use this workflow after a bulk import to confirm that the created content items are complete and consistent.
1. Ask the AI to inspect only the affected batch or a known list of content items.
2. Have it check for missing fields, missing assets, and unexpected workflow states.
3. Review the exceptions report before running any publish actions.
Example prompt:
```text copy
Review the Post items created during this migration batch. List any
items with missing required fields, missing assets, or an unexpected
workflow stage, and do not publish anything yet.
```
### Prepare content operations alongside code changes
Use this workflow when a release includes both implementation work and related content updates.
1. Give the AI access to the relevant repository or release notes.
2. Ask it to compare the implementation or new copy with the corresponding Prepr items.
3. Review the proposed content changes before asking it to apply them.
Example prompt:
```text copy
Compare the new feature copy in this repository with the corresponding
LandingPage items in Prepr. List the content items that need updating
and draft the recommended field changes before making any edits.
```
Source: https://docs.prepr.io/prepr-mcp-server/use-cases
---
# Common troubleshooting questions and answers
*Find answers to common questions and issues when connecting to and using the Prepr MCP server.*
## My AI client doesn't support OAuth. How do I connect?
If your client does not support OAuth for remote MCP connections, you need an MCP token instead.
Contact [Prepr support](mailto:support@prepr.io?subject=Request%20MCP%20Server%20token) and specify your environment.
Once you have your token, add the following authorization header when connecting:
`Authorization: Bearer YOUR_ACCESS_TOKEN`
Check out the [MCP server Authorization guide](/prepr-mcp-server/authorization) for more details.
## I get a connection error with the MCP server URL.
Make sure you're connecting to the MCP server URL: `https://mcp.prepr.io` in your client configuration.
The old path `https://mcp.prepr.io/mcp` path is deprecated.
## How do I reference a specific content item without its ID?
You can paste the Prepr URL of a content item or asset directly into your agent prompt.
The MCP server resolves the URL automatically, so you do not need to copy the item ID separately.
## What is the difference between update\_item and patch\_items?
- `update_item` replaces the full localized content item payload.
Use it when you want to overwrite all fields in a locale.
- `patch_items` only modifies supported root-level fields on an existing item without touching the rest of the payload.
Use it for targeted changes such as updating a workflow stage or reassigning an item.
Check out the [Available tools and actions](/prepr-mcp-server/available-tools-and-actions) for more details.
## Why is the AI returning content in the wrong locale?
The MCP server respects the locale you specify in your prompt.
If the AI is returning items in an unexpected locale, ask it to run `list_locales` first to confirm the exact locale codes available in your environment, then specify the target locale in your prompt using the correct code.
## Why can't the AI perform write operations?
Write access is tied to your Prepr user role.
If actions such as creating, updating, or publishing content items are unavailable when you manage content in Prepr directly, then your Prepr user role doesn't have the related write permissions.
For example, if the *Delete* permission is disabled for your role, the agent cannot delete content items, even if you ask it to.
Check out the [user role management guide](/project-setup/managing-roles-and-permissions#action-based-permissions) for more details.
## Can I use the MCP server to manage a schema?
Not yet.
The current MCP server supports content operations — reading, creating, updating, and publishing content items.
Schema management is on the roadmap and will allow AI assistants to inspect and manage content models through the MCP server in a future release.
Source: https://docs.prepr.io/prepr-mcp-server/troubleshooting
---
# Safety and limitations
Prepr MCP server is designed to help with editorial work, but write actions still need the right context.
Important behavior:
- Write actions depend on your [user role permissions](/project-setup/managing-roles-and-permissions#action-based-permissions)
- If you're connecting to a multi-locale environment, the AI agent should ask for clarification when it's not clear which locale to create or update a content item
- For a single-locale environment, include the locale in the AI agent's core instructions.
- If multiple candidate items match the request, the AI agent should ask for clarification before mutating content
- Deleting content always requires explicit confirmation
Current limitations:
- The `create_item` request creates one locale per call and supports model-level root fields of type `Text`, `Boolean`, `Integer`, `Float`, `Enum`, `Color`, `Tags`, `ContentReference`, `Asset`, and `ElementBox`
- The `update_item` request uses the same simplified input shape as `create_item`, but sends a full `PUT` update for one locale after first merging your changes into the current item
- The `patch_items` request supports model-level root fields of type `Text`, `Boolean`, `Integer`, `Float`, `Enum`, `Color`, `Tags`, and `ContentReference`
- The bulk mutation calls are processed in chunks of up to `25` content item IDs per call
## Currently known bugs and PR’s
- Resource needs to be added with payload examples for the dynamic content editor
Source: https://docs.prepr.io/prepr-mcp-server/safety-limitations
---
# Prepr MCP server
*The Prepr MCP server connects AI clients to your Prepr environment through the [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro).
It gives AI tools a safe, structured way to search content, create and update localized items, review publication state, manage workflow, and publish or unpublish content directly in Prepr.*
Source: https://docs.prepr.io/prepr-mcp-server
---
# API basics
*Check out this article to learn the basics of REST API.*
## The API URL
You can reach the REST API through two URLs. The first option is through our CDN, the second option is directly on the API.
Requests through the CDN are cached and are therefore always lightning fast. We recommend this URL for all front-end
applications that retrieve and display content items: `https://cdn.prepr.io/`
If you want to create, update or delete content items via the API we recommend the direct URL: `https://mutation.prepr.io/`
## The HTTP method
In general, the *Hypertext Transfer Protocol (HTTP)* is the underlying format that is used to structure requests and responses for effective communication between an application and a server.
To call the REST API, you will need to use the appropriate *HTTP method* with the `application/json` content type.
## Errors and validation
The REST API will validate and execute all inputs and return a response in JSON format.
When a request contains a mistake, the API might return an error message in the response. Read more about possible [statuses and errors](/mutation-api/statuses-errors).
## Read after write consistency
When you get a successful response for a mutation request, changes are persisted in Prepr. It is important to note that some changes are not visible immediately after the update. If you fetch content with a GraphQL API request right after a mutation with the REST API, you might get back stale content. Because, when you create, delete or update content the request is distributed around the globe with a short delay. Check out the [caching doc](/graphql-api/caching) for more details.
Source: https://docs.prepr.io/mutation-api/api-basics
---
# Authorization
*From this article, you’ll learn how to get access to the Prepr REST API.*
To access the Prepr API you first need to authenticate your app with an OAuth bearer token.
A token provides scoped access to a single environment, you need to obtain another token for every new environment you want to access.
Organizations with an Enterprise plan can create access tokens on the organization level.
We recommend using different access tokens for different applications or front end applications,
for example, one for an iOS app and another for Android app. This allows you to revoke them individually
in the future and manage access independently.
**Sign in to your Prepr account**
Go to [https://signin.prepr.io](https://signin.prepr.io) and sign in with your Prepr account credentials.
Then navigate to the Environment you want to use.
**Create an Access token**
1. Click the icon and choose the **Access tokens** option to open the access tokens page.
2. Click the **Add access token** button and choose the **REST API** option.

3. Enter the *Name*, and choose the required scopes from the *Rest API scopes* list, for example `content_items`.
4. Choose the *Expiration date* and click the **Save** button.

Once saved, you can copy the generated *Access token* value.
To authenticate the request include an `Authorization` HTTP header with the value of the access token you copied.
```http copy
curl -v https://cdn.prepr.io/{{endpoint}} -H 'Authorization: Bearer sdfg...'
```
Or
```http copy
curl -v https://mutation.prepr.io/{{endpoint}} -H 'Authorization: Bearer sdfg...'
```
If you fail to include a valid access token, the endpoint will return a status code 401.
Source: https://docs.prepr.io/mutation-api/authorization
---
# Statuses and errors
*This article describes the standard HTTP status and error codes the REST API returns.*
## HTTP response status codes
The REST API uses standard HTTP status codes to indicate whether a request is successful or not. In general, status codes within the *2xx range* indicate a successful request. When you receive an HTTP status code different from *2xx*, then you probably have one of the following issues:
| Status code | Description |
|-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
| **200 OK** | The request is successful. However, the API response might contain an error. For example, if your query is too complex or contains typos, etc. |
| **401 Unauthorized** | The access token is invalid or a scope is missing. |
| **400 Bad Request** | The request did not pass validation, please check the response for more information. For example, the `access_token_not_bound_to_user` error means the **Bound to user** value is empty on the [mutation API access token](/mutation-api/content-items-create-update-and-destroy) you created.|
| **404 Not Found** | The requested resource or endpoint could not be found. |
| **405 Method Not Allowed** | The requested HTTP method is not supported for the specified resource. |
| **429 Too Many Requests** | The access token sent too many requests in a given timeframe, check the HTTP headers to debug. |
| **5xx Internal Error or Service Unavailable** | Something went wrong on the Prepr servers. Try your request again after a few seconds. |
## Rate limits
**There are no rate limits enforced on requests that hit our CDN cache, i.e. the request doesn't count toward your rate limit,
and you can make an unlimited amount of cache hits.**
API Rate limits specify the number of requests an app can make to Prepr APIs in a specific time frame.
Every request counts against a per minute and a per hour rate limit.
By default, the Prepr API enforces rate limits of 35 req/sec, 800 req/minute, and up to 10K req/hour.
Higher rate limits may apply depending on your current plan.
When an access token or IP gets rate limited, the API responds with the `429 Too Many Requests` HTTP status code.
Use the HTTP headers in order to understand where the application is at for a given rate limit, on the method that was just utilized.
| header attribute | description |
| ------------- |-------------|
| X-Prepr-RateLimit-Limit | the rate limit ceiling for a one minute window
| X-Prepr-RateLimit-Remaining | the number of requests left for a one minute window
| X-Prepr-Retry-After | the remaining seconds before the rate limit resets
| X-Prepr-RateLimit-Reset | the remaining window before the rate limit resets, in UTC epoch seconds
Source: https://docs.prepr.io/mutation-api/statuses-errors
---
# Upgrade guide
*Learn more about our version support policy and the steps you need to take to upgrade your app to a newer API version.*
## Support policy
Prepr guarantees technical assistance and security updates for each new version of the REST API for at least 32 months.
Please note that **we do not change your API version automatically to avoid breaking your code**. Once you are ready to upgrade, please follow the instructions below.
## How to upgrade to a newer API version
Your API version controls the API behavior you see (for example, how your schema is generated and what fields you can request). When a major or breaking change is introduced to the API, Prepr releases a new version based on the release date. In this case, we encourage our customers to upgrade their API versions as soon as possible.
To upgrade to a newer API version, you need to generate a new access token for your Prepr environment as follows:
1. Go to **Settings → Access tokens** and click **Add access token**.
2. On the **New access token details** page, provide the name of the token.
3. Next, define permissions for this access token under **REST Permissions**. Permissions allow you to choose which content item statuses are accessible for an access token.
4. Click **Save** to confirm the settings.

Once you've created a new access token, you'll notice the new API version indicated on the **Access tokens overview** page. By default, the new access token uses the latest API version on the token generation date.
## Released REST API versions
You can check out the previous releases below.
| version | release date | end-of-life on |
|------------|--------------|------------------|
| 2023-11-02 | 2023-11-06 | t.b.a |
| all before | - | t.b.a |
### Version `2023-11-02`
This version adds Draft Mode support and performance updates.
#### What's new
- A new Content Item parameter `last_published_on` has been added. This field returns the timestamp of the last time a new version was published.
- The label for all Content Items has been updated from `Publication` to `ContentItem`.
#### Enabling Draft Mode
The following changes are active if you switch on Draft Mode.
**publishing**\
The `publish_on` field is only required when you acutely want to publish or schedule the current version of the item.
If the `workflow_stage` is set to Done, and you add a `publish_on` date/time the item will be published on that specified date/time.
When updating an existing content item, changing the `workflow_stage` to something else than Done will no longer
unpublish the item. To unpublish an existing content item, use the new unpublish endpoint.
```
PUT: /content_items/{id}/{locale}/unpublish
```
**status/workflow**\
The `status` field for a content item is renamed to `workflow_stage`. The response and request params are simplified:
```
// Draft Mode Off:
"status": {
"nl-NL": {
"id": "cff13c2a-615a-4f85-a50d-cc05104e6d6b",
"created_on": "2018-12-24T09:09:25+00:00",
"changed_on": null,
"label": "Status",
"body": "In progress"
}
}
// Draft Mode On:
"workflow_stage": {
"nl-NL": "In progress"
}
```
Please take in mind that if you request that `status` in your `?fields` param, you need to update this to `workflow_stage` too.
**webhook events**\
Everytime a new version is published the `content_item.published` event is sent to your webhook endpoint.
With drafts off this event is only triggered once (on first publish).
Source: https://docs.prepr.io/mutation-api/upgrade-guide
---
# Fetching single items
Use the Prepr REST API to fetch a single content item when you need to get specific content for a bulk process. For example, to get content from linked content items as part of a project to migrate content from a legacy system to Prepr CMS.
Before calling a REST API endpoint to fetch a single content item, go to **Settings → Access tokens** to create an access token with the *REST API scope* `content_items`.
To fetch a content item, send a `GET` request to the `https://cdn.prepr.io/content_items` endpoint. There are two ways to fetch a single content item.
## Query by Id
Since Ids are unique in Prepr, you will be able to find the content item you want using the `id` argument.
Here is an example that shows how to query a content item with the ID "535c1e-...".
```http copy
GET: /content_items/535c1e-4d52-4794-9136-71e28f2ce4c1
```
## Query by Slug
Since slugs are unique within a content model, you will be able to find a content item you want using the `slug` argument.
Here is an example that shows how to query a Page type content item with the slug "nieuws/...".
Source: https://docs.prepr.io/mutation-api/fetching-single-items
---
# Fetching multiple items
Use the Prepr REST API to fetch a collection of content items when you need to get content for a bulk process. For example, to get content from linked content items as part of a project to migrate content from a legacy system to Prepr CMS.
Before calling a REST API endpoint to fetch a collection of content items, go to **Settings → Access tokens** to create an access token with the *REST API scope* `content_items`.
To fetch content items, send a `GET` request to the `https://cdn.prepr.io/content_items` endpoint. Filter content items by the criteria listed below.
## Search
Prepr content items can be searched on their title or text fields.
### Title search
Filter content items by searching in the title field with fuzzy search.
```http copy
GET: /content_items?title[0][fz]=hello world
```
### Full text search
Filter content items by searching in all text fields.
```http copy
GET: /content_items?q[0][fz]=hello world
```
## Workflow stage
Prepr content items can be filtered on their workflow stage. You can query the workflow stage field by using the workflow stage as an argument.
## Locales
Prepr content items can be filtered on their locale. You can query the locale field by using the locales as an argument.
## Model
With Prepr you can have multiple models, like Article, Hero, Banner, Menu, Blog etc. All those different pieces can be defined as a Model.
## Dates
Prepr content items can be filtered on a different type of dates.
You can query the dates fields by using `publish_on`,`created_on`, `changed_on`, `expire_on` as an argument.
The examples below all use `publish_on`.
**Note:** all timestamps are UTC.
## Tags
Content items collections can be filtered on their tags.
You can query the tag fields by using the `tags` argument.
## Content Fields
Prepr content items collections can be filtered on a specific content model field value.
It's possible to apply advanced filters for the following field types: `Boolean`, `ContentIntegration`,
`Coordinates`, `DateTimeRange`, `Integer`, `Publication`, `Text`.
The filter options per type are different. It's also possible to combine multiple filters.
### Boolean
Get content items with boolean false:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"eq": 0
}
]
}
}
}
```
Get content items with boolean true:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"eq": 1
}
]
}
}
}
```
### Remote Content
Get content items that does contain any content integration item:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"has": true
}
]
}
}
}
```
Get content items that doesn't contain any remote source item:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"has": false
}
]
}
}
}
```
### DateTimeRange
Get content items that within or outside the given timestamp(s):
**Note**: You can only use this filter if field type is `Daterange`, not `Date and time`!
Options:
It's possible to combine the options below.
| argument | description |
| ------------- |-------------|
| `lt` | Less than from date. |
| `lte` | Less than or equals from date. |
| `gt` | Greater than from date. |
| `gte` | Greater than or equals from date. |
Greater than the from date
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"gt": 1600000000
}
]
}
}
}
```
Less than the from date
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"lt": 1600000000
}
]
}
}
}
```
In between than the from date
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"gt": 1600000000,
"lt": 1700000000
}
]
}
}
}
```
### Integer
Get content items with an integer with exact given value:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"eq": 10
}
]
}
}
}
```
### Content reference or Stack fields
Get content items that contains the given content item.
For example, when a web app visitor views the content item `10c503b4-e86b-422d-89d3-502b3faee34e`, you may want to display suggestions that are related to this publication.
Unlike [recommendations](/recommendations), these content items are directly linked to each other.
The filter options, `eq`, `in`, `has`, and `has not` result in linked content items.
### Type Text
Get content items with a text field with exact given value:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"eq": "Hello world!"
}
]
}
}
}
```
Get content items with a text field with match/search given value:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"fz": "world"
}
]
}
}
}
```
### Type List
Get content items with a list field with exact given value:
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"{{field id}}": [
{
"eq": "Hello world!"
}
]
}
}
}
```
### Combining multiple filters
It is possible to filter on multiple fields at the same time.
In this example we are going to filter on **boolean** (id: `premium`) and **integer** (id: `category_id`).
```json copy
{
"model": {
"id": "edc503b4-e86b-422d-89d3-502b3faee34e"
},
"items": {
"{{locale}}": {
"premium": [
{
"eq": 1
}
],
"category_id": [
{
"eq": 10
}
]
}
}
}
```
This will result in content items that are marked as premium and have an integer/category with value 10.
Source: https://docs.prepr.io/mutation-api/fetching-filtering-collections
---
# Working with fields
Use the `fields` argument in any REST API content items endpoint to choose the content fields that you want to see in the response.
## Combine fields
You can combine different fields in the `fields` argument of the request to choose all the content that you want to see in the response such as `items`, `title`, `model`, `locales`, `workflow_stage`, `environment`, `created_by`, `updated_by`, and `assigned_to`. See the example request and response below when combining some fields.
## Content fields
To request the main content include the field `items` in the `fields` argument. To see sub-fields of items, specify the `api_id` of the sub-field in the argument. Go to **Schema → Model → Field type settings** to get the `api_id` of the fields that you want to include. For example, a content item has a cover image field (type Assets) with api\_id `cover`.
In this example, you can access the sub-field `cdn_files` of your asset field in one request like in the example below.
## Field types
The list of field types returned in the `items` object of a content item depends on the related model. In the example responses below, you can see that the `api_id` of each field is returned in `items`.
Here is a list of field types to give you a better understanding of what to expect in the response of a `GET` request.
### Text
The Text field can be a single line, multiple lines, or HTML like in the example response below.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"title": {
// API Id of a single line text field
"id": "gmQh3QLgu80l1QLMg9qUj6nwr97YxqkbPhhUf7rO",
"created_on": "2023-01-01T09:56:21+00:00",
"changed_on": null,
"label": "Text",
"body": "Headline",
"format": null
},
"my_text_area": {
// API Id of a text area field with multiple lines
"id": "gmQh3QLgu80l1QLMg9qUj6nwr97YxqkbPhhUf7rO",
"created_on": "2023-01-01T09:56:21+00:00",
"changed_on": null,
"label": "Text",
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi u",
"format": null
},
"my_html_text": {
// API Id of an HTML text field
"id": "gmQh3QLgu80l1QLMg9qUj6nwr97YxqkbPhhUf7rO",
"created_on": "2023-01-01T09:56:21+00:00",
"changed_on": null,
"label": "Text",
"body": "Heading 1 Heading 2 Some bold text and italic
First bullet Second bullet Step 1 A useful link
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
",
"format": null
}
}
}
}
```
### Dynamic content
The Dynamic content field contains embedded videos, social media posts, maps, assets, and components for rich content items like in the example request below.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"content": { // The API Id of the dynamic content field
"items": [
{ // Embedded heading text in the dynamic content field
"id": "aa82833a-7b0c-413c-b8e5-e463efb32903",
"created_on": "2023-09-27T15:05:25+00:00",
"changed_on": null,
"label": "Text",
"body": "Ingredients",
"format": "H2"
},
{ // Embedded bullet points in the dynamic content field
"id": "d0ea8c21-0826-46ee-b559-3fb838daa1c7",
"created_on": "2023-09-27T15:05:25+00:00",
"changed_on": null,
"label": "Text",
"body": "first bullet point second bullet point ",
"format": null
},
{ // Embedded ordered list in the dynamic content field
"id": "f7e27135-f5b8-46e8-b103-f1f7807f7a6e",
"created_on": "2023-09-27T15:05:25+00:00",
"changed_on": null,
"label": "Text",
"body": "step 1 step 2 ",
"format": null
},
{ // Embedded table in the dynamic content field
"id": "6c8bc9fa-256c-499c-8636-3452cb02176a",
"created_on": "2023-09-27T15:05:25+00:00",
"changed_on": null,
"label": "Text",
"body": "Pros Cons Tasty Lots of calories Easy to make Need to use the oven
",
"format": null
},
{ // An embedded asset field
"label": "Asset",
"items": [
{
"id": "6059ecee-270f-46b0-b286-2e3e9374d33a",
"created_on": "2023-09-20T11:55:11+00:00",
"changed_on": "2023-09-20T13:12:27+00:00",
"label": "Photo",
"name": "blueprint",
"body": null,
"author": null,
"status": null,
"replaceable": true,
"reference_id": null,
"width": 1141,
"height": 1280,
"extension": "jpg",
"original_name": "blueprint",
"mime_type": "image/jpeg"
}
]
},
{ // An embedded location field
"id": "6059ecee-270f-46b0-b286-2e3e9374d33a",
"created_on": "2023-09-20T11:55:11+00:00",
"changed_on": "2023-09-20T13:12:27+00:00",
"label": "Coordinates",
"latitude": "52.3736018",
"longitude": "4.9002881"
}
],
"label": "ElementBox"
}
}
}
}
```
### Assets
Assets are images, videos, and audio files or documents that are linked to a content item.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"cover": { // API Id of the Asset field
"items": [
{
"id": "bac0aa60-dfa3-4f4e-9261-35a4fc693a76",
"created_on": "2022-11-23T12:50:33+00:00",
"changed_on": "2022-11-23T12:50:33+00:00",
"label": "Photo",
"name": "bg-cta-1",
"body": null,
"author": null,
"status": null,
"replaceable": true,
"reference_id": null,
"width": 1919,
"height": 939,
"extension": "png",
"original_name": "bg-cta-1",
"mime_type": "image/png"
}
],
"label": "Asset"
}
}
}
}
```
### Integer
Integers are whole numbers, and are often used to store stock quantities, prices in cents, etc. like in the example below.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"quantity": { // API Id of the Integer field
"id": "e356b423-71bc-40b5-8b6b-0611108658be",
"created_on": "2023-03-14T16:34:16+00:00",
"changed_on": null,
"label": "Integer",
"value": 122
}
}
}
}
```
### Float
Float types are number entries with decimal places, for accurate calculation such as the price of an item, distance, or weight.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"price": { // API Id of the Float field
"id": "69f6f04b-719e-49fd-b80c-b0037f495a31",
"created_on": "2023-03-14T16:34:16+00:00",
"changed_on": null,
"label": "Float",
"value": 122.12
}
}
}
}
```
### Boolean
The Boolean field has one of two possible values: true or false.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"needs_social_post": { // API Id of the Float field
"id": "4d32c2f1-69cd-4280-abe1-f5e548584417",
"created_on": "2023-03-09T09:45:23+00:00",
"changed_on": null,
"label": "Boolean",
"value": true
}
}
}
}
```
### Stack
The Stack field contains a list of (personalized) models/components. This is a powerful type that allows you to create a site using the Stack field as your page builder.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"stack": { // The API Id of a stack field
"items": [ // Contains all components and items in the stack
{ // A link to an existing Call to action content item
"id": "4c0622c1-f635-4a0a-83de-8f56bc5b9ea4",
"created_on": "2023-03-17T10:07:30+00:00",
"changed_on": "2023-03-17T10:07:30+00:00",
"label": "Publication",
"read_time": {
"en-GB": 1
},
"publish_on": {
"en-GB": "2023-03-17T10:07:00+00:00"
}
},
{ // Embedded component
"items": {
"description": {
"id": "006e3c8e-ea6b-44bd-886d-abcdf5357b6a",
"created_on": "2023-03-17T10:11:43+00:00",
"changed_on": null,
"label": "Text",
"body": "As far back as I can remember, I've always eaten french toast. It was usually a quick Sunday morning breakfast instead of pancakes or the go-to snack to use up the last stale bread slices. This is one of the easiest recipes that you can find for a quick treat.",
"format": null
},
"social_media_image": {
"items": [
{
"id": "54bbbdda-470b-4048-9a9e-0b3bca205003",
"created_on": "2023-03-09T14:16:39+00:00",
"changed_on": "2023-03-09T14:16:47+00:00",
"label": "Photo",
"name": "French toast",
"body": null,
"author": null,
"status": null,
"replaceable": true,
"reference_id": null,
"width": 3024,
"height": 4032,
"extension": "jpg",
"original_name": "4tq8iogbqhfi-pexels-sean-stevens-4623075",
"mime_type": "image/jpeg"
}
],
"label": "Asset"
},
"title": {
"id": "9a087714-324c-41cb-96c6-212f410cdf74",
"created_on": "2023-03-17T10:11:43+00:00",
"changed_on": null,
"label": "Text",
"body": "Excuse my french toast",
"format": null
}
},
"id": "1f3dec32-96e1-410a-bda8-22a634f9fd26",
"label": "PublicationElement"
// Label for component
}
]
}
}
}
}
```
### Content Reference
The Content reference field contains a link between one or more models like in the example below.
```json copy
{
"items": {
// The content item entry for this locale.
"en-US": {
// API Id of a content reference field
"authors": {
"items": [
{
// Id of the existing content item
"id": "a69bebd0-b438-41ab-8fc8-86353afaf57e",
"created_on": "2023-01-27T10:35:52+00:00",
"changed_on": "2023-01-30T08:43:51+00:00",
"label": "Publication",
"read_time": {
"en-GB": 1
},
"publish_on": {
"en-GB": "2023-01-27T10:35:00+00:00"
}
}
],
// Label for a content reference
"label": "Publication"
}
}
}
}
```
### Component
Components are often used to represent a set of reusable fields. Also, it can be added as a custom element to the Dynamic content or Stack fields.
```json copy
{
"items": {
// The content item entry for this locale.
"en-US": {
// The API Id of the embedded component
"seo": {
"items": {
// API Id of a text field in the component
"description": {
"id": "6eefa140-c2f4-499e-a236-794936eeb057",
"created_on": "2023-09-25T09:31:37+00:00",
"changed_on": null,
"label": "Text",
"body": "test",
"format": null
},
// API Id of the asset field in the component
"social_media_image": {
"items": [
{
"id": "6059ecee-270f-46b0-b286-2e3e9374d33a",
"created_on": "2023-09-20T11:55:11+00:00",
"changed_on": "2023-09-20T13:12:27+00:00",
"label": "Photo",
"name": "blueprint",
"body": null,
"author": null,
"status": null,
"replaceable": true,
"reference_id": null,
"width": 1141,
"height": 1280,
"extension": "jpg",
"original_name": "blueprint",
"mime_type": "image/jpeg"
}
],
"label": "Asset"
},
// API Id of a text field in the component
"title": {
"id": "baffc883-5104-4cc9-8813-c8920a943867",
"created_on": "2023-09-25T09:31:37+00:00",
"changed_on": null,
"label": "Text",
"body": "Test SEO title",
"format": null
}
},
// Component Id and label of the embedded component
"id": "85461317-fd8e-40f8-b453-dbf6ef9c68dd",
"label": "PublicationElement"
}
}
}
}
```
### Remote content
The Remote content field references content in an external CMS, legacy system or ecommerce platform. [Check out the Content Integration setup guide](/content-modeling/creating-a-custom-remote-source) for more details.
```json copy
{
"items": {
// The content item entry for this locale.
"en-US": {
// The API Id of the remote content field
"products": {
"items": [
{
"id": "1",
"body": "Pizza slicer",
"description": "This handy tool has more uses than you may realize. It can be used to slice your pizza and shape dough for the perfect pies.",
"image_url": "https://prepr-example-show-content-demo-patterns.stream.prepr.io/w_1920,h_1080/4frprlq6w4k6-pizza-slicer.png",
"data": {
"sku_id": 1,
"price": 1299,
"stock": 5
},
"content_integration": {
"id": "62cc3bec-6978-4ba6-a699-d379ff0a93de"
}
}
],
"label": "ContentIntegrationItem"
}
}
}
}
```
### Date & Time
The Date and time field adheres to the ISO 8601 standard. This field content differs in the response depending on the following field settings:
- **The Type** - This type can be either *Date*, *Date range* or *Business hours* which you can identify from the `label` like in the example below.
- **Time selection** - When time selection is enabled the format is `Y-m-d H:i:s` instead of `Y-m-d`.
- **Multiple dates** - When *Allow extra dates* is enabled, the field will be an `items` object like in the example below.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"event_date": {
// The API Id of a date field without time
"id": "73fd5c62-78b4-434a-966b-4aba04f3648a",
"created_on": "2023-01-01T16:25:53+00:00",
"changed_on": null,
"label": "DateTime",
"value": "2023-01-01",
"format": "Y-m-d"
},
"start_date_and_time": {
// The API Id of a date field with time
"id": "c1c5c7cf-d737-4fd9-9949-940b84cbf454",
"created_on": "2023-01-01T16:28:21+00:00",
"changed_on": null,
"label": "DateTime",
"value": "2023-01-01 18:25:00",
"format": "Y-m-d H:i:s"
},
"project_duration": {
// The API Id of a date field with ranges
"id": "abb46555-013f-4e2e-871e-344b243332f6",
"created_on": "2023-01-01T16:25:53+00:00",
"changed_on": null,
"label": "DateTimeRange",
"from": "2023-01-01",
"until": "2024-12-31",
"format": "Y-m-d"
},
"event_duration": {
// The API Id of a date field with ranges including time
"id": "efd799d9-ee9d-4ec1-8cc3-487b1ecb347a",
"created_on": "2023-01-01T16:25:53+00:00",
"changed_on": null,
"label": "DateTimeRange",
"from": "2023-01-01 00:00:00",
"until": "2024-12-31 23:59:59",
"format": "Y-m-d H:i:s"
},
"seasons": {
// The API Id of a date field with multiple date ranges
"items": [
{
"id": "2d701a86-7867-479a-bf09-36dbd847a36f",
"created_on": "2023-01-01T16:28:21+00:00",
"changed_on": null,
"label": "DateTimeRange",
"from": "2023-12-01",
"until": "2024-02-29",
"format": "Y-m-d"
},
{
"id": "72df271a-ebda-4372-b1ab-9d5ca36ef585",
"created_on": "2023-06-01T16:28:21+00:00",
"changed_on": null,
"label": "DateTimeRange",
"from": "2023-09-01",
"until": "2023-11-30",
"format": "Y-m-d"
},
...
],
"label": "DateTimeRange"
},
"opening_times" : {
// The API Id of a date field with Business hours type
"items": [
{
"id": "50425271-b86c-4d0a-886e-988642808d77",
"created_on": "2023-03-13T10:59:21+00:00",
"changed_on": null,
"label": "BusinessHours",
"state": "open",
"open_day": 1,
//Days are defined as 1 through 7, starting on Monday. So Monday would be 1, Tuesday would be 2, and so on.
"open_time": "00:00",
"close_day": 2,
"close_time": "00:00",
"valid_from": null,
"valid_until": null
},
{
"id": "c9509f72-e277-49e1-bbc5-2a31df543cae",
"created_on": "2023-03-13T10:59:21+00:00",
"changed_on": null,
"label": "BusinessHours",
"state": "open",
"open_day": 2,
"open_time": "04:30",
"close_day": 3,
"close_time": "01:30",
"valid_from": null,
"valid_until": null
},
{
"id": "9b602eb5-e376-4965-9837-d604350d0724",
"created_on": "2023-03-13T10:59:21+00:00",
"changed_on": null,
"label": "BusinessHours",
"state": "open",
"open_day": 2,
"open_time": "01:30",
"close_day": 2,
"close_time": "02:00",
"valid_from": null,
"valid_until": null
},
{
// Example of an exception to the regular business hours: Closed on Xmas day
"id": "37ff000f-a875-44a9-a91e-751700b1e599",
"created_on": "2023-03-13T14:59:21+00:00",
"changed_on": null,
"label": "BusinessHours",
"state": "closed",
"open_day": 4,
"open_time": "00:00",
"close_day": 4,
"close_time": "00:00",
"valid_from": "2025-12-25",
"valid_until": "2025-12-26"
}
],
"label": "BusinessHours"
}
}
}
}
```
### Location
The Location field contains Google Maps coordinates of a specific location in the content item.
```json copy
{
"items": {
"en-US": { // The content item entry for this locale.
"office_location": { // API Id of the location field
"id": "18e6f038-7766-4c65-8ef0-89e1c6a32926",
"created_on": "2023-01-01T16:05:08+00:00",
"changed_on": null,
"label": "Coordinates",
"latitude": 21.3137,
"longitude": -157.806
}
}
}
}
```
### Social
The Social field contains embedded social posts in the content item. The `label` depends on the social platform and will be one of these values: `TwitterPost`, `InstagramPost`, `YouTubePost`, `FacebookPost`, `VimeoPost`, `SoundCloudPost`, `SpotifyPlaylist`, `TikTokPost`, `ApplePodcast`.
```json copy
{
"items": {
"en-US": {
"weekly_social_post": {
// The Api Id of a social field in the content item
"id": "fe556b3f-20a1-416d-a6c7-baa7ccf29ead",
"created_on": "2023-01-01T10:04:57+00:00",
"changed_on": null,
"label": "TwitterPost",
"url": "https://twitter.com/SpaceX/status/1633941127081652224?s=20"
}
}
}
}
```
### Color
The Color field includes a HEX code.
```json copy
{
"items": {
"en-US": {
"border_color": {
// The Api Id of a color field in the content item
"id": "3035280e-c171-42cd-a643-c1fee2083543",
"created_on": "2023-03-13T11:49:58+00:00",
"changed_on": null,
"label": "Color",
"body": "#c73434",
"format": "Color"
}
}
}
}
```
### Tag
The Tag field includes tags like keywords for your content item.
```json copy
// The Tag field returns a list of tag items:
{
"items": {
"en-US": {
"search_keywords": { // The API Id of a Tags field in the content item
"items": [ // An items array with a list of tags
{
"id": "7ca6249b-7b63-4f96-a090-c92b5c297a11",
"created_on": "2023-03-09T09:45:23+00:00",
"changed_on": null,
"label": "Tag",
"body": "awesome",
"slug": "awesome"
},
{
"id": "7ca6249b-7b63-4f96-a090-c92b5c297a11",
"created_on": "2023-03-09T09:45:23+00:00",
"changed_on": null,
"label": "Tag",
"body": "great",
"slug": "great"
},
{
"id": "7ca6249b-7b63-4f96-a090-c92b5c297a11",
"created_on": "2023-03-09T09:45:23+00:00",
"changed_on": null,
"label": "Tag",
"body": "awesome and great",
"slug": "awesome and great"
},
],
"label": "Tag"
}
}
}
}
```
Source: https://docs.prepr.io/mutation-api/fetching-working-with-fields
---
# Paginating
Prepr returns collections of resources, such as content items, in a wrapper object that contains extra information useful for paginating overall results.
```json copy
{
"skip": 0,
"limit": 2
}
```
Will result in:
```json copy
{
"items": [{...},{...}],
"total": 98,
"skip": 0,
"limit": 2
}
```
In the above example, a client retrieves the next 25 resources by repeating the same request,
changing the `skip` query parameter to `25`. You can use the sort parameter when paging through
larger result sets to keep the order predictable. For example, `sort=-created_on` will order results by the time the resource was created.
## Limit
You can specify the maximum number of resources returned as a limit query parameter.
**Note:** The maximum number of resources returned by the API is 100. The API will throw a Bad Request for values higher than 100 and values other than an integer.
The default number of resources returned by the API is 25.
## Skip
You can specify an offset with the skip query parameter.
**Note:** The API will throw a Bad Request for values less than 0 or values other than an integer.
By combining skip and limit you can paginate through results:
`Page 1: skip=0, limit=15 Page 2: skip=15, limit=15 Page 3: skip=30, limit=15 etc.`
Source: https://docs.prepr.io/mutation-api/fetching-paginating-collections
---
# Sorting
You can use the `sort` argument to order your query results in a particular order.
```json copy
{
"sort": "created_on"
}
```
## Sort by metadata fields
It is possible to sort by the content items created, changed, published dates in either ascending or descending order.
The current values for sorting those fields are `created_on`, `-created_on`, `changed_on`, `-changed_on`, `publish_on`, `-publish_on`, `title`, `-title`.
## Default ordering
If you don't pass an explicit order value the returned content items will be ordered
descending by content item published timestamp `-publish_on`. This means that the most recently published
content item will appear at the top of the list.
Source: https://docs.prepr.io/mutation-api/fetching-sorting-collections
---
# Create & update content items
Use the Prepr REST API to do mutation on content items in bulk. For example, to create or update content items as part of a project to migrate content from a legacy system to Prepr CMS.
Before calling a REST API endpoint to create or update content items, go to **Settings → Access tokens** to create an access token with *REST API scopes* `content_items` and `content_items_publish`.
Set the **Bound to user** name to the user who's responsible for this mutation project.

If you already have an access token for a related mutation in the same workflow, for example, a content migration project, simply add the relevant *REST API scopes* to this access token.
## Create a content item
To create a content item, send a `POST` request to the `https://mutation.prepr.io/content_items` endpoint. Make sure the `Content-Type` in the header is set to `application/json` to send a valid JSON body. When the content item is created successfully, an auto-generated Id of the new content item will be returned in the response.
The metadata fields below need to be included in the body of the request.
|Field name| Description | Required (POST) | Required (PUT)|
|----------|----------------|--------------|---------------|
|`model`|The related model of the content item that you want to create. Go to the **Schema** tab and click the button at the top of the model and choose **Copy model ID**. Use this value to set the `id` field in the `model` object.| ✓|✗|
|`locales`| An array of the locales that are created in this request in ISO code (i18n) format, for example, `en-US` or `de-DE`. |✓|✓|
|`workflow_stage`| The content item workflow stage object with locales in it. Check out more details in the [workflow doc](/content-management/collaboration). To publish this content item immediately or on a `publish_on` date in the future, set the `workflow_stage` value to `Done`. This is required for a POST when drafts is enabled. If not, set `status` instead. |✓|✗|
|`status`| The content item status object with locales in it. If you have drafts enabled, this field is not required in a POST request and set the `workflow_stage` instead. |✓|✗|
|`slug`| Set a value for the slug of the content item. If you set a value that already exists, the API will generate a unique value. Check out [the slug field doc](/content-modeling/field-types#slug-field) for more details.|✗|✗|
|`publish_on`|This is the date when this content item needs to be published. To publish a content item immediately, set this value to the current date or a past date UNIX Timestamp.|✗|✗|
|`expire_on` | Set this date if you want a published content item to be archived.|✓|✗|
|`items`| The actual content fields are listed in this object and the field types in this list depend on the `model`. A `locale` in ISO code (i18n) format, for example, `en-US` or `de-DE` is the key for each content item entry like in the examples below.|✗|✗|
In the examples below, you can see requests based on the *Article* and *Person* models from the [*Blog* pattern](/content-modeling/examples/blog). You can also see the schema in a Prepr environment with Demo data or create a model and choose the *Blog* template.
### Person example
```json copy
{
"model": {
"id": "fd24889b-7dc8-4ed6-91ee-07b24609fdee"
},
"publish_on": {
"en-US": "1695286367"
},
"locales": [
"en-US"
],
"workflow_stage": {
"en-US": "Done"
},
/* If drafts is disabled, set "status" instead of "workflow_stage" as follows:
"status": {
"en-US": {
"body": "Done"
}
},
*/
"slug": {
"en-US": "renowned-blogger"
},
"items": {
// The key to create one content item entry for this locale.
"en-US": {
// The API id of a text field
"bio": {
"body": "This blogger is well-known all around the world."
},
// The API id of a text field
"full_name": {
"body": "Renowned Blogger"
},
// The API id of an assets field
"profile_pic": {
"items": [
{
// Id of an existing asset in this environment
"id": "cf91df18-df0e-4a20-b84a-8ef448becef9"
}
]
}
}
}
}
```
### Article example
```json copy
{
"model": {
"id": "b2298bbf-eda1-4f5c-9648-9213ecef6746"
// The Id of the Article model
},
"locales": [
"en-US"
],
"workflow_stage": {
"en-US": "In progress"
},
"slug": {
"en-US": "the-best-way-to-crack-an-egg"
},
"items": {
"en-US": { // Create one content item for this locale.
"authors": { // The content reference to the Person content item
"items": [
{
"id": "54515b1a-b6d6-4002-956a-39d809023921"
//This Id was in the response of the create request of the Person content item
}
]
},
// The API Id of the dynamic content field
"content": {
"items": [
{
// Need a label for each element in the dynamic content field
"label": "Text",
"body": "Ingredients",
"format": "H2"
},
{ // Embedded bullet points in the dynamic content field
"label": "Text",
"body": "first bullet point second bullet point "
},
{ // Embedded ordered list in the dynamic content field
"label": "Text",
"body": "step 1 step 2 "
},
{ // Embedded table in the dynamic content field
"label": "Text",
"body": "Pros Cons Tasty Lots of calories Easy to make Need to use the oven
"
},
{ // An embedded asset field
"label": "Asset",
"items": [
{
"id": "6059ecee-270f-46b0-b286-2e3e9374d33a"
// Id of an existing asset in this Prepr environment
}
]
},
{ // An embedded location field
"label": "Coordinates",
"latitude": "52.3736018",
"longitude": "4.9002881"
},
{
// Items object of a component
"items": {
"text": {
"body": "Everyone has their own way to crack an egg."
},
"title": {
"body": "The best way to crack an egg"
}
},
// The component Id for the embedded component
"id": "906ac62d-b59d-4346-ac7f-bf10a92fbb22"
},
]
},
// The API Id of the asset field
"cover": {
"items": [
{
// Id of an existing asset in this Prepr environment
"id": "6059ecee-270f-46b0-b286-2e3e9374d33a"
}
]
},
// The API Id of an HTML Text field
"excerpt": {
"body": "Whether it's for a brunch, dinner or tea party, quiche is the perfect addition and can be extremely easy to prepare and customize to suit your and your guests' taste buds.
"
},
// The API Id of a single line Text field
"title": {
"body": "Say quiche"
}
}
}
}
```
## Update a content item
To update an existing content item, send a `PUT` request to the `https://mutation.prepr.io/content_items/{id}` endpoint. Replace `{id}` with the Id of the content item to be updated. Make sure the `Content-Type` in the header is set to `application/json` to send a valid JSON body.
Keep the following in mind when you create your update request:
- Prepr doesn't merge content changes but completely updates a content item for a locale. So you must send the entire body for each locale that you want to update.
- When updating one locale, any other locales that are not in the update request remain unchanged.
- It's possible to update the `workflow_stage` of a locale without sending the items array.
See an example below of an update to the workflow stage of an existing *Article* content item.
```json copy
{
"locales": [
"en-US"
],
"workflow_stage": { // Update workflow stage to review
"en-US": "Review"
},
"assigned_to": { // Assign to a user to do the review
"en-US": {
"id": "1f71ff0a-f1f9-4cc3-85b4-bb8c7e33e4e0"
// The Id of the existing user in this Prepr environment
}
}
}
```
## Patch a content item
To only update specific fields on an existing content item, send a `PATCH` request to the `https://mutation.prepr.io/content_items/{id}` endpoint. Replace `{id}` with the Id of the content item to be updated. Make sure the `Content-Type` in the header is set to `application/json` to send a valid JSON body.
Keep the following in mind when you create your patch request:
- Only supported field types on the model level can be updated by the patch endpoint.
- The updates done by the patch endpoint will not update the `changed_on` metadata of an item. The update is processed silently and doest effect the order when sorting on `changed_on`.
- Field will be patched at root level, for example, if an Asset field is patched, you need to pass the full field content.
- The `PATCH` will update the latest version of an item, if this version is currently a DRAFT the update will be published when the editor publishes the new version. If the version is already PUBLISHED, changes will be live immediately.
- Webhook events are triggered the same as with standard updates.
- Required fields will not trigger validation rules.
See an example below of an update to the title of an existing *Post* content item.
```json copy
{
"locales": [
"en-US"
],
"slug": {
"en-US": "a-new-post-title"
},
"items": {
"en-US" : {
"title" : {
"body" : "A new Post title"
}
}
}
}
```
The `PATCH` request supports the following field types:
`Slug`,
`Text`,
`Enum`,
`Color`,
`Boolean`,
`Integer`,
`Float`,
`Coordinates`,
`Content reference`,
`Asset`,
`Date & Time` (single only),
`Social`,
`Form Embeds`,
`Resource`,
`Tag`
## Field types
The list of field types in the `items` array of a content item depends on the related model. In the example `POST` requests above, you can see that the `api_id` of a field is used to list the field array in `items`. Go to **Schema → Model → Field type settings** to get the `api_id` of the fields that you want to include in the mutation request. For a full list of field types and available settings like validation rules, check out the [Schema field types doc](/content-modeling/field-types) for more details.
Here is a list of field types that you might need to set up when you create or update a content item.
### Text
The Text field can be a single line, multiple lines, or HTML. Set the `body` values like in the example request below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// API Id of a single line text field
"title": {
"body": "Headline"
},
// API Id of a text area field with multiple lines
"my_text_area": {
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi u"
},
// API Id of an HTML text field
"my_html_text": {
"body": "Heading 1 Heading 2 Some bold text and italic
First bullet Second bullet Step 1 A useful link
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
"
}
}
}
}
```
### List (Enum)
The List field is a simple string with the content of the field. The content should match one of the values in the linked Enumeration.
Set the values like in the example request below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// API Id of a List field
"align": {
"body": "left"
}
}
}
}
```
### Dynamic content
Embed rich text, videos, social media posts, maps, and assets in this field to create rich content items like the [*Article* request example](#article-example).
Below is a list of elements that you can embed in the dynamic content field.
| Embedded element | Label | DESCRIPTION |
|------------------|---------|-----------------------------------------------|
| Text | `Text` | |
| Asset| `Asset`| Use an `items` object to embed an asset and set the `id` to an existing asset in Prepr like in the example above.|
| Location | `Coordinates` | Set the `latitude` and `longitude` values to embed a location. |
Check out the [Remote content](#remote-content) and [Social](#remote-content) field type details to embed these in your dynamic content field.
### Assets
Assets are images, videos, and audio files or documents that you can link to a content item. Set the `id` value to an existing asset in your Prepr environment like the example below. Check out the [Managing assets doc](/mutation-api/assets-upload-update-and-destroy) on how to upload assets using the REST API.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
//API Id of the Asset field
"cover": {
"items": [
{
// An Id of the asset that already exists in Prepr
"id": "d7261363-a656-4b8e-bc39-506e6d6ab365"
}
]
}
}
}
}
```
### Integer
Set an integer to store stock quantities, prices in cents, etc. like in the example below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the Integer field
"quantity": {
// The number value
"value" : 122
}
}
}
}
```
### Float
Set number entries with decimal places, for a price of an item, distance, or weight, etc. like in the example below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
//The API Id of the Integer field
"quantity": {
// The number value with decimal points
"value" : 122.22
}
}
}
}
```
### Boolean
Set a boolean field to true or false like in the example below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the boolean field
"needs_social_post": {
"value": true
},
}
}
}
```
### Stack
The Stack field includes a list of (personalized) models/components. Check out the [stack field docs](/content-modeling/field-types#stack-field) for more details. In the example below, you can see a request for a *Page* content item. Check out the related *Page* model on a Prepr environment with Demo data or create a model from the *Page* template.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of a stack field
"stack": {
// This element contains all components and items in the stack
"items": [
{
// Items object of a Page header component
"items": {
// The API Id of a text field in the component
"cta_label": {
"body": "Let's get baking!"
},
// The API Id of a text field in the component
"heading": {
"body": "Welcome to our baking community"
},
// The API Id of an asset field in the component
"image": {
"items": [
{
// The Id of an existing image in this Prepr environment
"id": "cf91df18-df0e-4a20-b84a-8ef448becef9"
}
]
},
// The API Id of a text field in the component
"text": {
"body": "Learn some basic steps to get started or kick your skills up a notch with our handy tips, recipes and products."
}
},
// Component Id of the page header component
"id": "bc2c106b-9e9d-43a8-b8c4-8d72866a9b4b",
},
{
// Items object of a Image and Text component
"items": {
"image": {
"items": [
{
// The Id of an existing image in this Prepr environment
"id": "6059ecee-270f-46b0-b286-2e3e9374d33a"
}
]
},
// The API Id of a List field
"image_position": {
"body": "Left"
},
"text": {
"body": "Everyone has their own way to crack an egg. Maybe they learnt it from a parent or from watching cooking videos. The truth is there is no \"best\" way. Practice makes perfect. Start with the back of a knife or fork and try not to get any shell pieces into your egg."
},
"title": {
"body": "The best way to crack an egg"
}
},
// The component Id for the image and text component
"id": "906ac62d-b59d-4346-ac7f-bf10a92fbb22",
},
{
// Link to an existing Call to action content item
"id": "51898d57-f507-475f-bc6b-7dc3405bbed5"
}
]
},
// The API Id of a text field in the content item
"title": {
"body": "Home page"
}
}
}
}
```
### Content reference
The Content reference field stores a link to one or more content items. Check out an Article example below with a content reference to a *Person* content item to link authors.
```json copy
{
"model": {
// The Id of the Article model
"id": "b2298bbf-eda1-4f5c-9648-9213ecef6746"
},
"locales": [
"en-US"
],
"workflow_stage": {
"en-US": "In progress"
},
"items": {
"en-US": {
// The API Id of the Content reference field
// for linked Person content items
"authors": {
"items": [
{
// The Id of an existing Person content item
"id": "54515b1a-b6d6-4002-956a-39d809023921"
}
]
},
"title": {
"body": "Say quiche"
}
}
}
}
```
### Component
Components are often used to represent a set of reusable fields. Also, it can be added as a custom element to the Dynamic content or Stack fields. In the example below we have an *Article* content item that has an *SEO* component.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the component field
"seo": {
// The list of fields based on the SEO component
"items": {
// The API Id of a text field in the component
"description": {
"body": "An easy french toast recipe"
},
// The API Id of an asset field in the component
"social_media_image": {
"items": [
{
// The Id of an existing asset in this Prepr environment
"id": "6059ecee-270f-46b0-b286-2e3e9374d33a"
}
]
},
// The API Id of a text field in the component
"title": {
"body": "The easiest french toast recipe"
}
},
// The component Id for the embedded component
"id": "906ac62d-b59d-4346-ac7f-bf10a92fbb22"
}
}
}
}
```
### Remote content
The Remote Source Response for a request with a Remote content field allows you to reference content in an external CMS, legacy system or ecommerce platform. [Check out the Remote source setup guide](/content-modeling/creating-a-custom-remote-source) to quickly setup your first integration.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the remote content field
"kitchen_shop": {
// The list of remote items for this content items
"items": [
{
// Id that matches the the item in the remote source
"id": "2",
// The main info about the item
"body": "Spatula",
"description": "We're willing to bet you reach for your spatula more often than you think. This tool is ideal for flipping the perfect pancake",
// The URL where the image is stored
"image_url": "https://prepr-example-show-content-demo-patterns.stream.prepr.io/w_1920,h_1080/2qanpdxyhf20-spatula.png",
"content_integration": {
// Id of the remote source from where the data will be synced
"id": "c0263fe3-007e-4307-9792-7364f6c0cf06"
}
}
]
}
}
}
}
```
Content integration items in Prepr have a predefined schema. This means that the type for any content integration item in the REST API schema follows the definition above.
### Date & Time
The DateTime field adheres to ISO 8601 standard. Check the field settings, to create or update this field correctly:
- **The Type** - The structure of this field is different depending on the *Type*: *Date*, *Date range* or *Business hours*.
- **Time selection** - When time selection is enabled, set the format to `Y-m-d H:i:s` instead of `Y-m-d`.
- **Multiple dates** - When Allow extra dates is enabled, put the content in an `items` object like in the example below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// API Id of a date field
"event_date": {
// Date only format
"format": "Y-m-d",
"value": "2023-01-01"
},
// API Id of a date time field
"start_date_and_time" : {
// Format with time selection
"format": "Y-m-d H:i:s",
"value": "2020-10-19 12:00:00"
},
// API Id of a date range field
"project_duration": {
// Date only format
"format": "Y-m-d",
"from": "2023-01-01",
"until": "2024-12-31"
},
// API Id of a date time range field
"event_duration": {
// Format with time
"format": "Y-m-d H:i:s",
"from": "2023-01-01 00:00:00",
"until": "2024-12-31 23:59:59"
},
// The API Id of a date range field with multiple date ranges
"seasons": {
"items": [
{
"from": "2023-12-01",
"until": "2024-02-29",
"format": "Y-m-d"
},
{
"from": "2023-09-01",
"until": "2023-11-30",
"format": "Y-m-d"
}
]
},
// API of a Date and time field for Business hours
"opening_times": {
// List of days and corresponding business hours
"items" : [
{
"state": "open",
// Number for corresponding day, 2 = Tuesday
"open_day" : 2,
// Opening time in 24 hour format
"open_time" : "08:00",
"close_day" : 2,
// Closing time in 24 hour format
"close_time" : "19:00"
},
{
"state": "open",
// Number for corresponding day, 3 = Wednesday, etc.
"open_day": 3,
// Opening time in 24 hours format
"open_time": "08:00",
"close_day": 3,
// Closing time in 24 hour format
"close_time": "19:00"
},
{
// Example of an exception to the regular opening hours: Closed on Xmas day
"state": "closed",
"open_day": 4,
"close_day": 4,
"valid_from": "2025-12-25",
"valid_until": "2025-12-26"
}
]
}
}
}
}
```
### Location
The Location field allows content editors to add Google Maps geo-points (coordinates or an address) to your content item.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the Location field
"event_location": {
// Latitude value in Google maps
"latitude": "21.3137",
// Longitude value in Google maps
"longitude": "-157.806"
}
}
}
}
```
### Social
Embed social posts in content items by adding a social URL. Set the `url` to a valid URL depending on the platform of the post.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the Social field in the content item
"cooking_posts": {
"url": "https://twitter.com/CookingChannel/status/1059520829841661954"
}
}
}
}
```
### Color
Add a Color field, by setting the HEX code like in the example request below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the color field
"border_color": {
// The Hex color code
"body": "#000000"
}
}
}
}
```
### Tag
Add tags (keywords) to your content item like in the example request below.
```json copy
{
"locales": [
"en-US"
],
"items": {
"en-US": {
// The API Id of the Tags field in the content item
"search_keywords": {
// An items array with a list of tags
"items": [
{
"body": "awesome",
},
{
// Id of an existing tag
"id": "3dbf4dbe-9c87-4bae-9b23-7ae685655ea1",
},
{
// slug of an existing tag
"slug": "more-than-great",
},
]
}
}
}
}
```
Source: https://docs.prepr.io/mutation-api/content-items-create-update-and-destroy
---
# Publish a single item
To publish or schedule a content item, use the following endpoint:
```http copy
PATCH: /content_items/{id}/{locale}/publish
```
Optionally a UNIX timestamp can be sent with the request to schedule the item to be published at a later time.
```json copy
{
// UNIX Timestamp
"publish_on" : 2342321312
}
```
If the request is successful the API will return status code 200.
This endpoint will trigger the `content_item.published` event.
Source: https://docs.prepr.io/mutation-api/content-items-publish
---
# Unpublish a single item
To unpublish a published version of a content item, use the following endpoint:
```http copy
PATCH: /content_items/{id}/{locale}/unpublish
```
If the request is successful the API will return status code 200.
This endpoint will trigger the `content_item.unpublished` event.
Source: https://docs.prepr.io/mutation-api/content-items-unpublish
---
# Delete a single item
There are two options to delete an existing content item.
You can either delete a specific language variant for a content item or delete the content item as a whole.
For both operations the scopes `content_items` and `content_items_delete` are required.
## Delete a language variant in the content item
To delete a specific language variant for a locale in the
existing content item, request the following endpoint with the HTTP `DELETE` method.
```http copy
DELETE: /content_items/94c56d8a-c54e-4b46-9971-a83bf06dcf52/en-US
```
Replace the `UUID` and the `locale` with the content item ID and locale you want to delete. If the request
is successful the API will return an empty response and status code 204.
## Delete the entire content item
To delete the content item as a whole, request the following endpoint with the HTTP `DELETE` method.
```http copy
DELETE: /content_items/94c56d8a-c54e-4b46-9971-a83bf06dcf52
```
Replace the `UUID` with the content item ID you want to delete. If the request
is successful the API will return an empty response and status code 204.
Source: https://docs.prepr.io/mutation-api/content-items-deleting
---
# Fetching assets
Prepr stores all your digital assets in the Media Library (in Prepr: the Media tab), making it easy to access,
view, and manage your assets. You can add, edit, download, or delete files whenever needed; categorize, search,
and filter assets on multiple attributes to find the right one quickly.
## The Asset object
```json copy
{
"id": "df7c1544-bad0-4c9d-928f-0c9f684c9ceb",
"created_on": "2023-06-13T14:35:54+00:00",
"changed_on": "2023-06-13T14:35:54+00:00",
"label": "Photo",
"name": "Sed et augue non mi dapibus tincidunt sollicitudin vel leo",
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel ornare massa. Ut vehicula commodo consequat.",
"author": "Marc van Dam",
"status": null,
"reference_id": "dam-249387",
"width": 1020,
"height": 1020,
"file_size": 228286, // File size in bytes. Value available for assets uploaded since 23 December 2025
"original_name": "sed-et-augue.jpg",
"mime_type": "image/jpeg",
"cdn_files": {
"total": 1,
"items": [
{
"id": "bfaac48b-fb0d-4d12-84da-af8d65244c4b",
"created_on": "2023-06-12T08:44:43+00:00",
"label": "CdnFile",
"file": "371apne44zy3.jpg",
"url": "https://example.stream.prepr.io/{format}/371apne44zy3-5az.jpg"
}
]
},
// Additional fields in the Asset model for the default locale
// Boolean field to mark an asset copyrighted
"copyrighted" : true,
"author_name": {
"body" : "John Captura", //An HTML text field for Author name
},
"price": 12.50, // Number field for price
// Additional fields in the Asset model for other locales
// Pass the localized param to get the fields for other locales
"localized" : {
"de-DE" : {
"copyrighted" : true
}
}
}
```
| FIELD | DESCRIPTION |
|---------------|------------------------------------------------------|
| id | Unique identifier for each item. |
| created\_on | UTC time at which the asset was created/upload. |
| changed\_on | UTC time at which the asset was last updated. |
| label | Identifier for the object. |
| name | Name of the asset. |
| body | Description of the asset. |
| reference\_id | Custom asset API ID. |
| author | Optional name of the author. |
| width | Width of the image of video. |
| height | Height of the image of video. |
| original\_name | Name of the uploaded file. |
| mime\_type | Mime type of the original file. |
| cdn\_files | Object containing an array of `items`, the url field can be used for media playback. |
## Query by ID
Since IDs are unique in Prepr, you can find the asset you want using the id argument.
Here is an example that demonstrates how to query an asset with the ID "535c1e-...":
```http copy
GET: /assets/535c1e-4d52-4794-9136-71e28f2ce4c1
```
Source: https://docs.prepr.io/mutation-api/fetching-single-assets
---
# Fetching assets
Prepr stores all your digital assets in the Media Library (in Prepr: the Media tab), making it easy to access,
view, and manage your assets. You can add, edit, download, or delete files whenever needed; categorize, search,
and filter assets on multiple attributes to find the right one quickly.
```http copy
GET: /assets
```
## Search
Prepr assets can be searched on their title or text fields.
```http copy
GET: /assets?q[0][fz]=Beach Club
```
## Types
Assets can be filtered on their type.
Available options are: `Photo`, `Video`, `Audio`, `Document`.
## Tags
Assets can be filtered on their tags.
You can query the tag fields by using the `tags` argument.
## Content Item relation
Assets can be filtered on their relation to content items.
## Collections
Assets can be filtered on their relation to collections.
You can query the collections fields by using the `collections` argument.
```http copy
GET: /assets?collections[0][eq]={ID}
```
## Query by Reference ID
The *Reference ID* is useful for storing external asset Ids. For example, during the migration of assets from another system. Check out the [Migration doc](/project-setup/migrating-content) for more details.
Here is an example that demonstrates how to query an asset with a *Reference ID* value.
```http copy
GET: /assets?reference_id[0][eq]=123456
```
# Sorting
You can use the `sort` argument to order your query results in a particular order.
```json copy
{
"sort": "created_on"
}
```
## Sort by metadata fields
It is possible to sort by the assets created, changed dates in either ascending or descending order.
The current values for sorting those fields are `created_on`, `-created_on`, `changed_on`, `-changed_on`.
## Default ordering
If you don't pass an explicit order value the returned assets will be ordered
descending by asset changed on timestamp `-changed_on`. This means that the most recently changed
asset will appear at the top of the list.
Source: https://docs.prepr.io/mutation-api/fetching-multiple-assets
---
# Managing assets
Use the REST API to do bulk mutation of assets when the [Media library UI](/content-management/managing-assets/managing-assets) is not an efficient option. For example, to upload assets as part of a project to migrate content from a legacy system to Prepr CMS.
Before calling a REST API endpoint to manage the assets, go to **Settings → Access tokens** to create an access token with *REST API scopes* defined below.
If you already have an access token for a related mutation in the same workflow, for example, a content migration project, simply add the relevant *REST API scopes* to this access token.
## The Asset object
When uploading or updating an asset, you can include some additional information about the asset in the body of your request like in the example below.
```json copy
{
"name": "Example cover image",
// If the name is not filled, the API will use the file name in this field.
"body": "This is an asset that I uploaded using REST API.",
// A description for this asset.
"author": "Image Designer",
// The author of the asset.
"reference_id": "12345678",
// An external ID for the asset, for example an ID from a legacy system.
"tags": { // Include a list of related keywords to find the asset easily
// For each tag, set either the ID, body or slug
"items": [
{
"id": "3dbf4dbe-9c87-4bae-9b23-7ae685655ea1"
// ID of an existing tag in Prepr
},
{
"body": "dynamic"
// If this tag doesn't exist, it will automatically be created
},
{
"slug": "dynamic-page"
// If this tag doesn't exist, it will automatically be created
}
]
}
"collections": { // Add this asset to a collection of similar assets
"items": [
{
"id": "ebc7fd2c-2458-4f2f-b7f5-5487f63d412a",
// ID of an existing collection. Check out the Collections doc for more details.
}
]
}
}
```
## Upload new files
Add the *REST API scopes* `assets` and `assets_publish` to the access token. The event `asset.created` will be fired when a new asset is created.
### Upload Image + Document + Audio
To upload documents, photos, audio files, or cover images, send a `POST` request to the `https://mutation.prepr.io/assets` endpoint.
- If the files are available locally, add the `"source"` parameter to specify a file like in the example below. Make sure the `Content-Type` in the header is set to `multipart/form-data` to send a valid asset file.
* If the assets are located on a different server, set the `url` parameter to the URL location of the file like in the example code below. In this case, make sure the `Content-Type` in the header is set to `application/json`. You can upload files up to 25MB using the `url` parameter.
When the upload is successful an auto-generated ID of the new asset will be returned in the response of this request.
This ID can then be used to fetch the asset if needed.
### Upload Video
To upload videos follow the steps below to upload each video in chunks. Make sure to handle errors and resume the upload of any remaining chunks before finishing the upload of a video.
#### Step 1: Split the video into chunks
First split each video into 25MB (26214400 bytes) chunks using the bash command below.
```bash copy
split -b25m {filename}
```
#### Step 2: Start an upload session
Start a resumable upload by initializing a new asset object. To make a start request and
create a video upload session, send a `POST` request to the `https://mutation.prepr.io/assets` endpoint. Set the parameters as shown in the example below.
When the upload is started successfully an auto-generated ID of the new asset will be returned in the response of this request.
This ID can then be used in the request to upload the chunks and finish the request.
#### Step 3: Upload chunks
Now that the upload session has started and you have chunks ready to upload, make a transfer request to upload each chunk in the order that they are split.
To upload the first video chunk, send a `POST` request to the `https://mutation.prepr.io/assets/{id}/multipart` endpoint and set the parameters like in the example below. Replace `{id}` with the ID returned in the response of the previous request.
#### Step 4: Complete the upload
Once you upload all chunks, make a finish request to complete the upload, post the video, and queue it for asynchronous video-encoding.
Send a `POST` request to the `https://mutation.prepr.io/assets/{id}/multipart` endpoint and set the parameters like in the example below.
Replace `{id}` with the ID returned in the response of the [start session request](#step-2-start-an-upload-session).
## Update
To update an existing asset, add the *REST API scopes* `assets` and `assets_publish` to the access token.
Send a `PUT` request to the `https://mutation.prepr.io/assets/{id}` endpoint. The `asset.changed` event will be fired when the asset is changed.
Source: https://docs.prepr.io/mutation-api/assets-upload-update-and-destroy
---
# Delete a single asset
To delete the asset, request the following endpoint with the HTTP `DELETE` method.
The scopes `assets` and `assets_delete` are required.
```http copy
DELETE: /assets/94c56d8a-c54e-4b46-9971-a83bf06dcf52
```
Replace the `UUID` with the asset ID you want to delete. If the request
is successful the API will return an empty response and status code 204.
Source: https://docs.prepr.io/mutation-api/delete-single-asset
---
# Asset Collections
A Collection represents a set of media files grouped by specific attributes and includes all types of assets.
It can be a collection of images, videos, documents or audio files.
## The Collection object
```json copy
{
"id": "df7c1544-bad0-4c9d-928f-0c9f684c9ceb",
"created_on": "2023-06-13T14:35:54+00:00",
"changed_on": "2023-06-13T14:35:54+00:00",
"label": "Collection",
"body": "Branding & backgrounds"
}
```
| FIELD | DESCRIPTION |
|---------------|------------------------------------------------------|
| id | Unique identifier for each item. |
| created\_on | UTC time at which the asset was created/upload. |
| changed\_on | UTC time at which the asset was last updated. |
| label | Identifier for the object. |
| body | Name of the collection. |
## Create, update or destroy
### Create a new collection
To create a collection.
```http copy
POST: /collections
```
### Update an existing collection
To update a collection.
```http copy
PUT: /collections/{id}
```
### Destroy an existing collection
To destroy a collection.
```http copy
DELETE: /collections/{id}
```
## Managing collections
### Add assets to a collection
To add assets to an existing collection, use the example below. This process only adds new assets to the collection. If this collection already has assets, these remain in the collection unchanged and duplicate entries are simply ignored.
```json copy
{
"items" : [
{
"id" : "234h-432847-x23498-763x4-324x234" // The Asset ID, you can add mulitple assets at once.
},
{
// "id" : "30f064f4-8acb-4ee9-9e79-81d7029cc0c7"
}
]
}
```
```http copy
POST: /collections/{id}/assets
```
### Remove assets from a collection
To remove assets from an existing collection, use the example below.
```json copy
{
"items" : [
{
"id" : "234h-432847-x23498-763x4-324x234" // The Asset ID.
}
]
}
```
```http copy
DELETE: /collections/{id}/assets
```
### Set a collection cover
To set a cover of the collection.
```http copy
POST: /collections/{id}/cover?id={assetId}
```
## Scopes
`collections`, `collections_publish`, `collections_delete`
Source: https://docs.prepr.io/mutation-api/assets-collections
---
# Resizing
## Introduction
The Images API is a read-only API for delivering images to apps, websites and other touchpoints.
The Images API is available via a global CDN. The server closest to the user serves all content,
which minimizes latency and especially benefits mobile apps. Hosting content in multiple global data
centres also improves the availability of content.
You can request images in specific sizes and crops.
Images are returned with a `cdn_files` field. This object returns a `url` param that you can use as a basis for resizing the image needed for your app.
The URL contains a part that is returned as `{format}` you need to replace this with the desired format.
| argument | type | required | description |
|----------|-------------| -------------| -------------|
| `w` | String | false | Defines the width. |
| `h` | String | false | Defines the height. |
| `q` | String | false | Defines the image quality. |
| `c` | String | false | Defines the crop. Default: `centre`. Options: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center`, `centre` |
The format is constructed as `{option}_{value}` so if we would want to get the image in a 200px width form, `{format}` should be replaced with `w_200`.
To simplify this it's possible to let the API construct those URLs for you. Just add them as an extra field in the `cdn_files` field. This extra field is called `resized`.
## Example
We want a `landing` image of 232x232 and a `thumb` of 123x123.
The fields are constructed as followed:
```http copy
GET: /assets/{id}?fields=cdn_files{resized{landing.width(232).h(232),thumb.width(123).h(123)}}
```
Example response:
```json copy
{
// Particle response of /assets endpoint.
"cdn_files": {
"total": 1,
"items": [
{
"id": "bfaac48b-fb0d-4d12-84da-af8d65244c4b",
"created_on": "2023-06-12T08:44:43+00:00",
"label": "CdnFile",
"file": "371apne44zy3.jpg",
"url": "https://example.stream.prepr.io/{format}/371apne44zy3-5az.jpg"
"resized": {
"landing": "https://example.stream.prepr.io/w_232,h_232/371apne44zy3.jpg",
"thumb": "https://example.stream.prepr.io/w_123,h_123/371apne44zy3.jpg"
}
}
]
}
}
```
This will add an extra param(s) to the object with the pre-rendered URL. It is possible to add up to 3 different formats into the request.
Source: https://docs.prepr.io/mutation-api/assets-resizing
---
# Managing asset lifecycles
*This guide provides step-by-step instructions on managing asset lifecycles in Prepr with support for custom transcoding or streaming services. Available exclusively to enterprise plan customers.*
## The asset lifecycle
The below stages make up the simple asset lifecycle.
## Integrating with external platforms
The steps below shows you how to handle key events when integrating with external platforms.
## Adding a cover thumbnail
You can use the standard mutation API `post` endpoint to add a cover to your asset.
Check out the [Manage assets doc](/mutation-api/assets-upload-update-and-destroy#upload-new-files) for more details.
Source: https://docs.prepr.io/mutation-api/assets-integration
---
# Fetching segments
Segmentation organizes visitors into specific groups based on shared characteristics or similar
behavior that matter for your use cases. These groups represent different target audiences for your business,
making it easier to deliver more relevant content and experiences to your web app visitors.
## The Segment object
```json copy
{
"id": "df7c1544-bad0-4c9d-928f-0c9f684c9ceb",
"created_on": "2023-01-23T14:34:59+00:00",
"changed_on": "2023-01-23T14:34:59+00:00",
"label": "Segment",
"body": "Marketing campaign YYY",
"reference_id": "utm-source-google-ads",
"query": "{\"viewed\":[{\"param\":\"Body\",\"publication_tags_in\":[\"google-ads\"]}]}"
}
```
| FIELD | DESCRIPTION |
|--------------|-------------------------------------------------------------|
| id | Unique identifier for each item. |
| created\_on | UTC time at which the segment was created. |
| changed\_on | UTC time at which the segment was last updated. |
| label | Identifier for the object. |
| body | Name of the segment. |
| reference\_id | Custom segment API ID. |
| query | JSON of the segment conditions. |
## Fetching all segments
```http copy
GET: https://mutation.prepr.io/segments
```
The pagination of the segments endpoint is identical to the [content items endpoints](/mutation-api/fetching-paginating-collections).
## Query by ID
Since IDs are unique in Prepr, you can find the segment you want using the id argument.
Here is an example that demonstrates how to query a segment with the ID "535c1e-...":
```http copy
GET: https://mutation.prepr.io/segments/535c1e-4d52-4794-9136-71e28f2ce4c1
```
Source: https://docs.prepr.io/mutation-api/segments
---
# Fetching tags
A *Tag* is a meaningful label you can assign to your content items, visitors and assets to differentiate between them while filtering.
## The Tag object
```json copy
{
"id": "df7c1544-bad0-4c9d-928f-0c9f684c9ceb",
"created_on": "2023-01-23T14:34:59+00:00",
"changed_on": "2023-01-23T14:34:59+00:00",
"label": "Tag",
"body": "Amsterdam",
"slug": "amsterdam"
}
```
| FIELD | DESCRIPTION |
|------------|-------------------------------------------------------------|
| id | Unique identifier for each item. |
| created\_on | UTC time at which the segment was created. |
| changed\_on | UTC time at which the segment was last updated. |
| label | Identifier for the object. |
| body | Name of the tag. |
| slug | Custom tag API ID. |
## Fetching all tags
```http copy
GET: /tags
```
The pagination of the tags endpoint is identical to the [content items endpoints](/mutation-api/fetching-paginating-collections).
## Query by ID
Since IDs are unique in Prepr, you can find the tag you want using the id argument.
Here is an example that demonstrates how to query a tag with the ID "535c1e-...":
```http copy
GET: /tags/535c1e-4d52-4794-9136-71e28f2ce4c1
```
## Managing tags
### Fields
| argument | type | required | description |
| ------------- |-------------| -------------| -------------|
| `body` | String | true | Defines the tag. |
```json copy
{
"body": "Dog"
}
```
### Create
To create a tag.
```http copy
POST: /tags
```
### Update
To update a tag.
```http copy
PUT: /tags/{id}
```
### Destroy
To destroy a tag.
```http copy
DELETE: /tags/{id}
```
## Tag Groups
### Fetching all tag groups
```http copy
GET: /tag_groups
```
### Managing tag groups
#### Fields
| argument | type | required | description |
|-----------|--------|----------|-------------------------------------------|
| `body` | String | true | Defines the name of the tag group. |
| `tags` | Object | false | Defines the tags inside of the tag group. |
```json copy
{
"body": "Taggroup 1",
"tags": {
"items": [
{
"id": "00a92e60-24bd-4908-a36c-96268465111f",
"body" : "Amsterdam"
}
]
}
}
```
#### Create
To create a tag group.
```http copy
POST: /tag_groups
```
#### Update
To update a tag group.
```http copy
PUT: /tag_groups/{id}
```
#### Destroy
To destroy a tag group.
```http copy
DELETE: /tag_groups/{id}
```
Source: https://docs.prepr.io/mutation-api/tags
---
# Fetching customers
Customers are all persons who have interacted with your content. Meaning all persons who have read, clicked, shared, bookmarked, or commented on content items. A customer profile is created for each person. You can manage these profiles in Prepr. With this, we enable you to segment and personalize content for your customers.
## The Customer object
To expand the customer object when querying a customer add the field name below in the `fields` parameter.
```json copy
{
"id" : "234h-432847-x23498-763x4-324x234",
"first_name": "Jhon",
"last_name": "Doe",
"date_of_birth": "2000-12-01",
"email" : "jhon.doe@gmail.com",
"phone" : "31641356222",
"tags": {
"items": [
{
"body": "Amsterdam"
}
]
}
}
```
| field name | type | required | description |
|------------------|--------|----------|-----------------------------------------------------------------------|
| `id` | String | - | - |
| `first_name` | String | false | Lists the first name of the customer. |
| `last_name` | String | false | Lists the last name of the customer. |
| `date_of_birth` | String | false | Lists the date of birth of the customer. Format: `Y-m-d`. |
| `email` | String | false | Email address of the customer. |
| `phone` | String | false | Phone number of the customer. |
| `reference_id` | String | false | Lists the reference\_id of the customer. |
| `tags` | Object | false | Lists tags of the customer. |
| `segments` | Object | false | Lists the segment the customer is in. |
## Fetching all customers
If you want to fetch a collection of customers.
```http copy
GET: https://customers.prepr.io/
```
How to filter the customs is explained on the [Filtering customers](/mutation-api/customers-query-all) page.
## Query by ID
Since IDs are unique in Prepr, you can find the customer you want using the id argument.
Here is an example that demonstrates how to query an customer with the ID "535c1e-...":
```http copy
GET: https://customers.prepr.io/{{uuid}}?fields=custom,email,phone,tags
```
Source: https://docs.prepr.io/mutation-api/customers
---
# Filtering the customer list
If you want to fetch a collection of customers.
```http copy
GET: https://customers.prepr.io/
```
## Text search
Filter customers by searching in name, company and email fields.
```json copy
{
"q" : [
{
"fz" : "Donald T"
}
]
}
```
## Query by email
Find a customer by using an email address.
```json copy
{
"email_eq" : "mail@example.com"
}
```
## Query by reference ID
Find a customer by using a Reference ID.
```json copy
{
"reference_id_eq": "323c93d0-dd2c-40d1-90fb-7454ea06761d"
}
```
## Query by segments
Prepr customers can be filtered on segments. You can query the segments field by using the `segments` as an argument.
### Customers that are in the specified segment
This example requests all customers in the specified segment.
```json copy
{
"segments": [
{
"eq": "323c93d0-dd2c-40d1-90fb-7454ea06761d" // Segment Id
}
]
}
```
### Customers that are in on of the following segments
This example requests all customers in one of the specified segments. If a customer is in both segments
it will be returned once.
```json copy
{
"segments": [
{
"in" : [
"323c93d0-dd2c-40d1-90fb-7454ea06761d",
"8c4b2a7b-9827-4e57-8bd5-1ef04a046238"
]
}
]
}
```
## Query by tags
If you want to filter customers by related tags.
### Customers that have a specified tag
```json copy
{
"tags": [
{
"eq": "323c93d0-dd2c-40d1-90fb-7454ea06761d" // Tag Id
}
]
}
```
### Customers that have one of the following tags
```json copy
{
"tags": [
{
"in" : [
"323c93d0-dd2c-40d1-90fb-7454ea06761d",
"8c4b2a7b-9827-4e57-8bd5-1ef04a046238"
]
}
]
}
```
### Customers that have all of the following tags
```json copy
{
"tags": [
{
"all" : [
"323c93d0-dd2c-40d1-90fb-7454ea06761d",
"8c4b2a7b-9827-4e57-8bd5-1ef04a046238"
]
}
]
}
```
### Customers that have none of the following tags
```json copy
{
"tags": [
{
"nin" : [
"323c93d0-dd2c-40d1-90fb-7454ea06761d",
"8c4b2a7b-9827-4e57-8bd5-1ef04a046238"
]
}
]
}
```
## Expanding fields
To request more customer data check out the [Query by ID](/mutation-api/customers#query-by-id) page.
## Sorting customers
You can use the `sort` argument to order your query results in a particular order.
```json copy
{
"sort": "created_on"
}
```
It is possible to sort the customers by created, changed, last seen dates in either ascending or descending order.
The current values for sorting those fields are `created_on`, `-created_on`, `changed_on`, `-changed_on`, `last_seen`, `-last_seen`.
## Pagination
Prepr returns collections of resources in a wrapper object that contains extra information useful for paginating overall results.
```json copy
{
"skip": 0,
"limit": 2
}
```
Will result in:
```json copy
{
"items": [{...},{...}],
"total": 98,
"skip": 0,
"limit": 2
}
```
In the above example, a client retrieves the next 100 resources by repeating the same request,
changing the `skip` query parameter to `100`. You can use the sort parameter when paging through
larger result sets to keep the order predictable. For example, `sort=-created_on` will order results by the time the resource was created.
### Limit
You can specify the maximum number of resources returned as a limit query parameter.
**Note:** The maximum number of resources returned by the API is 1000. The API will throw a Bad Request for values higher than 1000 and values other than an integer.
The default number of resources returned by the API is 100.
### Skip
You can specify an offset with the skip query parameter.
**Note:** The API will throw a Bad Request for values less than 0 or values other than an integer.
By combining skip and limit you can paginate through results:
`Page 1: skip=0, limit=15 Page 2: skip=15, limit=15 Page 3: skip=30, limit=15 etc.`
Source: https://docs.prepr.io/mutation-api/customers-query-all
---
# Create, update & destroy customers
## The Customer object
```json copy
{
"first_name": "Jhon",
"last_name": "Doe",
"date_of_birth": "2000-12-01",
"email": "jhon.doe@gmail.com",
"phone": "31612345678",
"tags": {
"items": [
{
"body": "Amsterdam"
}
]
}
}
```
| argument | type | required | description |
|------------------|-------------| -------------| ------------|
| `id` | String | | |
| `first_name` | String | false | Defines the first name of the customer. |
| `last_name` | String | false | Defines the last name of the customer. |
| `date_of_birth` | String | false | Defines the date of birth of the customer. Format: `Y-m-d`. |
| `email` | String | false | Defines email address of the customer. |
| `phone` | String | false | Defines phone number of the customer. |
| `reference_id ` | String | false | Defines the reference\_id of the customer. |
| `tags ` | Object | false | Defines tags of the customer. |
## Create
To create a customer.
```http copy
POST: https://customers.prepr.io/
```
Scopes: `customers` `customers_publish`
## Update
To update an existing customer.
```http copy
PUT: https://customers.prepr.io/{id}
```
Scopes: `customers` `customers_publish`
## Destroy
To delete a customer.
```http copy
DELETE: https://customers.prepr.io/{id}
```
Scopes: `customers` `customers_delete`
Source: https://docs.prepr.io/mutation-api/customers-create-update-and-destroy
---
# Signing-up customers
Customers represent anonymous and registered people that are engaging with your content.
They usually represent website visitors or shop buyers.
The Customer API provides methods to get, create, update and delete.
## Creating a new customer
You can create a new Customer in your Prepr environment.
```http copy
POST: https://customers.prepr.io/
```
### Fields
| argument | type | required | description |
|------------------|-----------| -------------|---------------------------------------------------------------------------|
| `first_name` | String | false | Defines the first name of the customer. |
| `last_name` | String | false | Defines the last name of the customer. |
| `date_of_birth` | String | false | Defines the date of birth of the customer. Format: `Y-m-d`. |
| `source` | String | false | Mostly used to define imports etc. |
| `email` | String | false | Email addresses of the customer. |
| `phone` | String | false | Phone number of the customer. |
| `reference_id ` | String | false | Defines the reference\_id of the customer. |
| `tags ` | Object | false | Defines tags of the customer. |
| `sign_in ` | Boolean | false | Will add a sign in token to the response after creating the new customer. |
```json copy
{
"first_name": "Jhon",
"last_name": "Doe",
"date_of_birth": "2000-12-01",
"source" : "ios app",
"email": "jhon.doe@gmail.com",
"phone": "31612345678",
"tags": {
"items": [
{
"body": "Amsterdam"
}
]
}
}
```
Source: https://docs.prepr.io/mutation-api/sign-up-introduction
---
# Sign-in with a magic link
How to Build Magic Link sign-in.
## Setting-up the email template
**Creating a HTML template**\
You can create your own email template to be sent to your customers.
First, create an HTML template using your preferred code editor. Then follow the steps below to add it Prepr.
**Signing in to your Prepr account**\
Go to [https://signin.prepr.io](https://signin.prepr.io) and sign in with your Prepr account.
Then navigate to the Environment you want to create the Sign-In for.
**Save the email template**\
Go to **Settings → Email templates** and click **Add template**.
Enter a name, and a reply email address like this `Prepr `.
Paste the HTML you created into the body field and click save.
Copy the `Id` (on the left side of the page), you need this later.
## Request the Magic Link
After a customer has clicked on the "Sign-In" link on your front-end, make a
request to the Customer API to sent the Magic Link.
```json copy
{
"email" : "ryan.vaughan@example.prepr.io",
"email_template" : {
"id" : "a8d0cd02-c339-46fa-8602-6a5cbf991487"
},
"redirect_url" : "https://example.com/sign_in"
}
```
Replace the email template ID with the ID from the template you created in step 1.
```http copy
POST https://customers.prepr.io/request_sign_in
```
If the request is successful the customer receives a magic link in their mailbox.
## Implementing Sign-In handler
If the customer clicks on the sign-in link, the API will redirect the customer back to your site.
The API will add a query param `access_token` to the url.
```http copy
https://example.com/sign_in?access_token=
```
This token is a `temporary sign-in token`. To use this for following requests we need to
convert it to an `Personal Access Token`.
This is pretty simple, just create a POST request to `https://customers.prepr.io/sign_in_with_magic`
with the received token as the Authentication Bearer header.
This will result in a new Personal Access Token for the customer:
```json copy
{
"id": "a22287ff-7277-4583-8adb-0ca3e55e21b8",
"last_seen": "2021-03-01T15:35:58+00:00",
"first_name": "Ryan",
"last_name": "Vaughan",
"access_token": {
"access_token": "Gniw9Mt90cLj7136M5ao0B7mGHHMHXXw68NlKMyuiinmVU426Dw",
"token_type": "Bearer",
"expires_in": null
}
}
```
**Done!** 🥳 You've completed the sign-in.
Source: https://docs.prepr.io/mutation-api/sign-in-magic-link
---
# Fetching a customer profile
After a customer signs in, you can query the customer profile using the following endpoint.
```http copy
GET: https://customers.prepr.io/customers/me
```
## Customer object
To expand the customer object when querying a customer profile, add the following field names to the fields parameter.
### Fields
| field name | type | description |
|------------------|--------|-----------------------------------------------------------------------|
| `id` | String | - |
| `first_name` | String | Lists the first name of the customer. |
| `last_name` | String | Lists the last name of the customer. |
| `date_of_birth` | String | Lists the date of birth of the customer. Format: `Y-m-d`. |
| `email` | String | Email addresses of the customer. |
| `phone` | String | Phone number of the customer. |
| `reference_id ` | String | Lists the reference\_id of the customer. |
| `tags ` | Array | Lists tags of the customer. |
| `segments ` | Array | Lists the segment the customer is in. |
```http copy
GET: https://customers.prepr.io/me?fields=custom,emails,tags
```
```json copy
{
"id" : "234h-432847-x23498-763x4-324x234",
"first_name": "Jhon",
"last_name": "Doe",
"date_of_birth": "2000-12-01",
"email": "jhon.doe@gmail.com",
"phone": "31612345678",
"tags": {
"items": [
{
"body": "Amsterdam"
}
]
}
}
```
Source: https://docs.prepr.io/mutation-api/customers-fetching-customer-profile
---
# Signing-out customers
Sometimes you will find yourself needing to log your customer out. Here is a small example of how to do so.
To invalidate the session make an `HTTP Delete` request to `https://customers.prepr.io/sign_out`
with the Personal Access Token as the Authentication Bearer header.
If the session is destroyed, the API will reply with a `204 No Content` status code.
Source: https://docs.prepr.io/mutation-api/sign-out
---
# Resending webhooks
In some cases you may need to re-trigger multiple webhook requests for your front end or an integration.
This can be accomplished by triggering the following endpoint.
```http copy
GET /content_items/bulk/webhooks
```
## Parameters
All filtering parameters from the content items collection endpoint can be added to this request.
| argument | type | required | description | default |
|--------------|:-------|------------|------------------------------------------------------------------------------------------------------------------------------|----------|
| `event` | String | true | Defines the webhook event. Possible values are: `content_item.created` `content_item.changed` `content_item.published` `content_item.unpublished` `content_item.deleted` | |
| `webhook_id` | Object | false | Defines which webhooks should be triggered. | |
| `skip` | Int | false | You can specify an offset with the skip query parameter. | 0 |
| `limit` | Int | false | You can specify the maximum number of items as a limit query parameter. The highest limit you can set is 1000. | 25 |
### Resending one webhook
The example below resends webhook events for all the content items with the tag `1fb70a05-7cab-4f7e-bae7-bbf7f69fd091` or
`f9b73994-76bf-49a0-87f8-505252e21086` to a webhook with the ID `312cae95-9343-43e8-bf4a-93898261acd7`.
```json copy
{
"event" : "content_item.published",
"webhook_id" : [
{
"eq": "312cae95-9343-43e8-bf4a-93898261acd7"
}
],
"limit" : 1000,
"skip": 0,
"tags" : [
{
"in" : [
"1fb70a05-7cab-4f7e-bae7-bbf7f69fd091",
"f9b73994-76bf-49a0-87f8-505252e21086"
]
}
]
}
```
### Resending multiple webhooks
The example below resends webhook events for all the content items with one of the specified tags to two webhook endpoints.
```json copy
{
"event" : "content_item.published",
"webhook_id" : [
{
"in": [
"312cae95-9343-43e8-bf4a-93898261acd7",
"312cae95-9343-43e8-bf4a-93898261acd7",
]
}
],
"limit" : 1000,
"skip": 0,
"tags" : [
{
"in" : [
"1fb70a05-7cab-4f7e-bae7-bbf7f69fd091",
"f9b73994-76bf-49a0-87f8-505252e21086"
]
}
]
}
```
## Response
The API response includes arrays of IDs for queued content items and webhooks. Additionally, it provides a count for queued webhooks events labeled as `total_queued` and a count for the number of filtered content items labeled as `total_items`. This supports straightforward pagination of content items when necessary.
Source: https://docs.prepr.io/mutation-api/bulk-webhooks
---
# Mutation API Reference
The Prepr REST API is a Content Delivery and Mutation API.
All responses are cached by our API CDN, caches will be cleared if anything changes in your Prepr environment. Contact support@prepr.io to get help implementing your application, or join our development [Slack](https://slack.prepr.io).
Source: https://docs.prepr.io/mutation-api
---
# Start working with Drafts
Drafts is turned off by default if your environment has REST API access tokens. No changes are made to the functioning of the API endpoints.
Keep the following in mind when activating Drafts.
## What's changed
### Publish On
If drafts is switched off (and the current behaviour), every content item requires you to post and update it with a publish\_on date/time.
When you start using drafts the publish on field is only required when you acutely want to publish the current version of the item.
If the workflow\_stage is set to Done, and you add a publish\_on date/time the item will be published on that specified date/time.
#### Note
Please take in mind that if you until now items always had a `publish_on` param on requests, from now this is optional.
### The Status field
When you start using draft the `status` field for a content item is renamed to `workflow_stage`. Also the response is simplified:
Using the `status` field:
```
"status": {
"nl-NL": {
"id": "cff13c2a-615a-4f85-a50d-cc05104e6d6b",
"created_on": "2018-12-24T09:09:25+00:00",
"changed_on": null,
"label": "Status",
"body": "In progress"
}
}
```
Using the new `workflow_stage` field:
```
"workflow_stage": {
"nl-NL": "In progress"
}
```
#### On creating a new content item
When creating a new content item the input for the workflow\_stage field is the same as the new simplified response.
#### On updating an existing content item
When updating an existing content item, changing the workflow\_stage to something else than Done will no longer
unpublish the item. To unpublish an existing content item, use the new unpublish endpoint.
#### Note
Please take in mind that if you request that `status` in your `?fields` param, you need to update this to `workflow_stage` too.
## Webhooks
When you enabled drafts, everytime a new version is published the `content_item.published` event is sent to your webhook endpoint.
## What's new
### Filtering on published state content items
To filter our all content items that are not published, use the following new filter:
```
"published_status" : {
"eq" : "published"
}
Options are: `published`, `not published`, `scheduled`
```
If you use the new REST API scope `content_items_published` this filter is automatically applied with the `published` value.
### Unpublishing an existing version
To unpublish an existing item.
```
PUT: /content_items/{id}/{locale}/unpublish
```
### The published version field
If a content item has a version published you can easily resolve the version by using the new field `published_version`.
```
"published_version": {
"nl-NL": {
"id": "203405a9-9330-4116-89ec-905a63ed34f7",
"created_on": "2023-09-28T12:03:42+00:00",
"publish_on": "2023-09-28T12:03:00+00:00",
"expire_on": null,
"body": "7bcfgjao58ri"
}
}
```
Source: https://docs.prepr.io/developing-with-prepr/start-working-with-drafts
---
# Adding the Tracking Code (manual method)
To create a tracking code manually, please follow the steps below:
1. Copy the following code snippet:
```html copy
```
2. Replace `PREPR_TRACKING_ID` with your actual access token taken from **Settings > Access tokens**.
3. Add the code to the **\** section of your website.
Once you've set up the tracking pixel, proceed with [recording events](/data-collection/recording-events).
Source: https://docs.prepr.io/developing-with-prepr/tracking-code-manual-method
---
# Develop with Prepr
Experience how easy it is to develop with Prepr CMS.
Source: https://docs.prepr.io/developing-with-prepr
---
# Changelog 2025
Find the beautiful features and important updates that were added to Prepr in 2025. This changelog gives you an insight into the most eye-catching releases during this period.
## Introducing *Impact Goals* for personalization and A/B testing
*Impact Goals* allow you to see the overall impact of your personalization and A/B testing experiments on conversions.
You can create clear conversion definitions by combining multiple behavioral conditions.

This means you can optimize your website not just for click-through rate (CTR), but for deeper insights.
So you can better understand what works for customer experience, boosting engagement and conversion rates.
Check out the [Goals setup guide](/personalization/defining-goals) for more details.
## New *Content tree* layout
You can now view your overall structure of content items, by their slug (URL) value, as a visual tree layout.
The *Content tree* layout provides instant, hierarchical context for how an item relates to other content.

This means improved navigation between content items and less time searching for related items.
For more details, check out the [content tree setup guide](/project-setup/setting-up-environments#content-tree-parent-slug-format).
## Generate AI suggestions for personalized variants
When adding personalized content, you can now get AI suggestions for adaptive content based on a customer segment you choose.
The auto-generated variant suggestions give you inspiration to quickly create and fine-tune your adaptive content.

The AI generation of adaptive content allows you to automate normally repetitive work of re-writing content for multiple customer segments.
This means you can scale your personalization efforts in much less time.
Check out the [Adaptive content guide](/personalization/managing-adaptive-content#add-an-adaptive-content-element) for more details.
## Dynamic initial values for text fields
With dynamic initial values you can set up text fields to prefill based on other field values helping editors speed up content creation.
For example, for *Post* content items, you can define the initial value for the SEO meta description field to be prefilled with the Post excerpt value the editor enters in the content item.

This means simpler data entry and ensures consistency across content items.
Check out the [text field settings](/content-modeling/field-types#text-field) for more details.
## Improved GraphQL caching
We've improved our GraphQL API caching mechanism to significantly improve developer experience and workflow reliability when making changes to your *Schema*.
Now, the GraphQL API automatically refreshes the cache when you make any change to a schema.
This means that your changes are immediately applied when you regenerate your TypeScript types in front-end application.
Check out the [GraphQL API caching doc](/graphql-api/caching) for more details.
## Introducing granular permissions to manage content items
With granular content permissions, you can now precisely define a user role to allow users to only create, read, update, delete, manage comments, publish and unpublish content items.

In doing so, you have enhanced security and clearer workflow for every user reducing the risk of accidental errors.
Check out the [user role guide](/project-setup/managing-roles-and-permissions#add-or-edit-roles) for more details.
## Prepr now available in French
With Prepr now available in French, your users in French-speaking regions or those who prefer French can use Prepr in their native language.
You can enable the French UI in the environment settings or each user can set their preferred interface language to French in their [account profile](/project-setup/managing-users#set-language-preferences).

This feature provides a more intuitive and comfortable user experience, leading to greater understanding, and reduced errors for French-speaking users.
For more details, check out the [environments settings](/project-setup/setting-up-environments#manage-environment-settings).
## Making slug prefix read-only
We've introduced a new option to make the first part of a slug read-only.
When you enable this option in the *Slug* field settings, editors can only edit the part of the slug after the last `/`.

With this feature, you enforce consistent slugs ensuring clean and accurate content item URLs and prevent broken links when editors need to update slug values manually.
Check out the [*Slug* field reference](/content-modeling/field-types#slug-field) for more details.
## Conditional visibility by environment
You can now control field visibility per environment when using a shared schema.
This new option lets you choose which environments to show certain fields, ideal for multi-site or multi-brand setups where fields differ slightly between environments.

It makes managing shared schemas much more flexible and keeps your content models clean and relevant across all sites.
Check out the [shared schema guide](/project-setup/architecture-scenarios/shared-schema#choose-field-visibility-for-environments) for more details.
## New SSO login options
You can now integrate your preferred identity provider (IdP) to Prepr using one of our new SSO options.
In addition to *Microsoft Entra ID* (formerly *Azure Active Directory*), you can now set up single sign-on for your Prepr users using *Google Workspace*, or any identity provider with either the *SAML 2.0* or *OpenID Connect* open standards.

By setting up one of these options, you enhance security and give your users an improved login experience.
Check out the [updated SSO guide](/project-setup/setting-up-sso) for more details.
## Improved navigation user interface
We've improved our navigation with a cleaner and more intuitive interface.
The environment selector is now on the left and you'll see a prominent banner to indicate when you're logged into a test or development environment.
You'll also find the *Settings* in a new location on the right when you click the icon.

The updated navigation allows you to work more confidently in Prepr with the clear indication of the environment and type of environment you're working in.
For more details, check out the [environments guide](/project-setup/setting-up-environments).
## Publish nested content items
You now have the option to publish all linked child items in one go when publishing a parent content item.

This prevents broken or incomplete pages and ensures all linked content goes live at the same time.
So, you save time and avoid errors by publishing the whole content structure in a single action.
Check out the [content management docs](/content-management/managing-content/managing-content-items#publish-a-content-item) for more details.
## Introducing filter options for content selection
You can now see filter options in the content item selection modal when you choose to add related content items to your content item.

These options allow you to quickly narrow down large lists of content items by key criteria like model, category, or workflow stage.
So, you save time and ensure you link related content items more accurately.
Check out the [adding content references doc](/content-management/managing-content/creating-rich-content#adding-content-references) for more details.
## Introducing custom workflow stages
We've added the ability for you to add custom workflow stages to your Prepr collaboration workflow on the *Environment* detail page.
For example, when you have translation tasks for content items, you could add stages like *Translate* and *Review translation*.

This means you can seamlessly align the Prepr workflow with your own content creation process.
For more details, check out the [environment settings doc](/project-setup/setting-up-environments#workflow-stages).
## New GraphQL API version 2025-10-07 is available
Our newest GraphQL API version brings you additional localization support with the following new features:
- A new root query field `DefaultLocale` returns the environment's default locale.
- `_locale` in the Interface `Model`
- `_locales` in the Interface `Model`
Check out the [GraphQL API upgrade guide](/graphql-api/upgrade-guide#version-2025-10-07) for more details.
## Introducing the Shopify integration
Our new Shopify integration lets your team easily include products, variants and collections in content items.

This means your content editors can work more efficiently and error-free by simply choosing the relevant Shopify entries directly in Prepr.
For more details, check out the [Shopify integration guide](/integrations/shopify).
## Introducing the BigCommerce integration
Now you can integrate Prepr with BigCommerce to seamlessly add product details to your content.

With this integration, your content editors can save time by simply choosing BigCommerce products directly in Prepr.
For more details, check out the [BigCommerce integration guide](/integrations/bigcommerce).
## Automatically review your content items
Introducing *Content check*, a new feature that automatically validates the quality of your content and provides AI-powered suggestions for improvements.

In just one click, it checks for missing required fields, broken links and options to boost your SEO.
Try out this new tool, part of our continuous efforts to optimize the quality of the content in less time, and [let us know your thoughts](https://prepr.io/feedback).
Check out the [Reviewing content guide](/content-management/reviewing-content#content-check) for more details.
## Introducing the Snitcher integration
With the new Snitcher integration, you can easily connect Prepr to Snitcher, a B2B website visitor identification platform.
This integration allows you to segment website visitors based on their company profile.

So, you can personalize content for an enhanced user experience for your B2B audience.
Check out the [Snitcher integration guide](/integrations/snitcher) for more details.
## New sign-in option to use passkeys
We've released a new sign-in option to use passkeys.

This feature gives you an alternative to using the email, password combination and 2FA.
This means you can log in securely and conveniently without needing to manually manage passwords or additional authentication steps.
For more details, check out the [account profile settings](/project-setup/managing-users#add-passkeys).
## New option to hide models from *Add item* list
You can now simplify the content creation experience by reducing the list of models content editors need to select from.
To make this possible, choose which models to hide from the selection list in the *Add item* modal.
For example: Hide models for child content items like an *Author* or *Category* which are usually created when editing an *Article*.

By disabling the visibility of certain models, you ensure content editors only see what's relevant to them.
Check out the [Model settings](/content-modeling/managing-models#settings) for more details.
## Organize your segments into folders
You can now group your segments into folders, making it easier to manage and navigate when working with many customer profiles.
Instead of scrolling through a long alphabetical list, related segments can be organized together in a structured way.

This update gives you a simple way to stay organized and find the right segment faster.
For more details, check out the [Managing segments guide](/personalization/managing-segments#organize-segments-into-folders).
## Matching HubSpot contacts to Prepr customer profiles
Prepr now automatically matches your HubSpot contacts to existing Prepr customer profiles, putting them in their matching HubSpot segments and updating their company name and email address.
This way, you ensure your HubSpot customer profiles are always accurate and up to date.

With this release, you can create personalized, data-driven content even more precisely.
Check out the [HubSpot integration](/integrations/hubspot) guide for more details.
## Algolia Auto sync option
With the new Algolia **Auto sync** option you can choose to disable automatic synchronization of content to Algolia directly in Prepr.

This gives you more control and helps you prevent issues with the web app search functionality resulting from bulk updates, for example.
In other words, ensure a stable search experience for your users.
Check out the [Algolia integration guide](/integrations/algolia#connect-prepr-to-algolia) for more details.
## A/B testing article headlines
You can now run experiments for article headlines by adding an A/B test directly to a header or title *Text field*.
For example, to test headlines in a list of recommended articles on the home page.
Now you can measure which headlines encourage readers to open the article they want to read.

Optimize performance with surgical precision, driving higher engagement and conversion rates.
To enable A/B testing on a text field check out the [Text field settings](/content-modeling/field-types#ab-testing).
## New Prepr Next.js package version is available
We're happy to bring you a new and improved version of the **Prepr Next.js package**, a toolkit to streamline your personalization and A/B testing implementation for your Next.js front end.
We’ve redesigned the *Preview Bar* into a less intrusive floating toolbar, giving content editors a cleaner, distraction-free preview experience.

In addition to previewing different A/B test variants and personalized experiences, content editors can now enable **Edit mode** to highlight any element in the preview page.
When enabled, they can then simply hover over elements to reveal links that open the corresponding item directly in Prepr for quick updates.
Check out the [Prepr Next.js package guide](/prepr-nextjs-package) for more details.
## Improved handling of conditional required fields
We’ve released an important update to ignore the required field validation when a field is conditionally hidden.
Previously, if a required field was hidden due to conditional logic, it would still prevent a content item from being published.
This often led users to mark fields as non-required simply to avoid validation errors during publishing.
With today’s update, a required field is only enforced when it is visible to the content editor.
If the field is hidden due to a conditional setting, it'll be treated as not required in the API schema.
However, fields that are always visible remain required both in the editor and the API.

This improvement is enabled by default for new customers, while existing environments can activate the feature manually in the **Environment** settings page.
Check out the [environment settings](/project-setup/setting-up-environments#manage-environment-settings) for more details.
## Introducing content item views
With the new content item views, we're introducing a cleaner interface to manage content visibility.
This new feature allows you to create, organize, and access your own tailored views.

The new content item views replace how you previously used saved filters for content items.
You can save time by creating views that are most relevant for you, while your team can collaborate more effectively with shared and role-based views.
Check out the [managing content doc](/content-management/managing-content/managing-content-items#views) for more details.
## Introducing the *Needs attention* overview
To enhance the reliability and quality of stored content, we've added a content quality checker to find content items with broken links or content items that could not be published.
You can find the list of these content items in the new *Needs attention* view.

These quality indicators help prevent broken user experiences on live websites and empower content teams to quickly identify and resolve issues before publication.
Check out the [managing content doc](/content-management/managing-content/managing-content-items#manage-content-quality) for more details.
## Recovering deleted content items
With the new *Deleted* view, you can now quickly find and restore deleted items with just a few clicks, for example, if you deleted an item accidentally.

Check out the [managing content doc](/content-management/managing-content/managing-content-items#recover-a-deleted-item) for more details.
## Two-factor authentication (2FA) required for Owners and Admins
To strengthen account security, 2FA is required for all users with an *Owner* or *Admin* role in a paid Prepr organization as of July 1, 2025.
If 2FA is not enabled, users will be prompted to set it up before accessing Prepr. SSO users are exempt from this requirement.
Check out the [Activating two-factor authentication](/project-setup/managing-users#activate-two-factor-authentication) guide for more details.
## New Identify event to store identity provider user IDs
The new `Identify` event stores a user's unique ID from your Identity Provider directly in their matching customer profile in Prepr.
```js copy
prepr('event', 'Identify', 'external-profile-ID');
```
This event allows you to accurately link customer data when they log in to your web app giving you more precise data analysis.
For more details, check out the [recording events doc](/data-collection/recording-events#using-identify-providers).
## Introducing the Pipedrive integration
With the new Pipedrive integration, you can embed Pipedrive forms directly into your content items.

This means you keep your content and related Pipedrive forms in one place.
Just enter the matching Pipedrive form URL to link the form you need.
Check out the [Pipedrive integration doc](/integrations/pipedrive) for more details.
## New PATCH mutation endpoint in public beta
We’ve introduced a new `PATCH` endpoint for content items in the Mutation API, allowing you to update just a single field in an
existing item instead of replacing the whole object. This aligns with HTTP PATCH best practices for partial updates and efficiency.
Plus, thanks to refined metadata handling, this endpoint won’t trigger the `changed_on` timestamp — so batch updates no longer
clutter the editor interface.
For more details, check out the [Mutation API doc](/mutation-api/content-items-create-update-and-destroy#patch-a-content-item).
## Introducing the Dealfront (Leadfeeder) integration
We’re excited to introduce a new B2B integration in Prepr with Dealfront.
This powerful new integration lets you segment website visitors based on their industry and company size.

This allows you to personalize content for an enhanced user experience for your B2B audience.
For more details, check out the [Dealfront integration guide](/integrations/leadfeeder).
## New enumeration JSON editor
We've introduced a new option to add or edit enumeration values using a JSON editor.

This new option enables you to quickly access and copy the data structure and add your own updated JSON.
This saves time, especially when you need to add or edit an enumeration with an large list of values.
Check out the [enumerations doc](/content-modeling/managing-enumerations) for more details.
## Personalization across all Stack fields
Previously, you could add personalization and A/B test content only to a stack field directly included in the model.
We've enhanced the *Stack* field to allow personalization and A/B test content at any level in the content structure.
These include stack fields in components and within dynamic content fields.
For example: In a CTA button component to only personalize the button instead of the whole section of the content item where it's used.

This feature gives you more flexibility for adaptive content in the web app and an improved user experience.
To use this feature, make sure to use the newest [GraphQL API version 2025-05-27](/graphql-api/upgrade-guide#version-2025-05-27).
## Dynamic content in components
Previously, you could only add a dynamic content field to a model.
It's now possible to add a *Dynamic content* field to a component to add rich content sections to your section-based pages. For example, to publish a guide with your chosen styling.

This feature allows more flexibility when editing content and more dynamic content delivery in the web app.
To use this feature, make sure to use the newest [GraphQL API version 2025-05-27](/graphql-api/upgrade-guide#version-2025-05-27).
## New GraphQL API version 2025-05-27 is available
Our newest GraphQL API version brings you more flexibility when creating a schema and developing an adaptive web app with the following new features:
- Support for A/B tests and adaptive content elements within *Stack* fields in components, allowing for more dynamic content delivery and an improved user experience.
- The *Dynamic Content Editor* field is now available in components, enabling you to create engaging and personalized content easily.
- We’ve introduced the *Tags* field in components, providing a flexible way to categorize and manage your content more effectively.
- You can now access a default query to retrieve the locales available in your environment, making it easier for you to implement localization in your front end.
- We've enhanced sorting on `string` fields to be case-insensitive, ensuring a more intuitive and user-friendly experience.
Check out the [GraphQL API upgrade guide](/graphql-api/upgrade-guide#version-2025-05-27) for more details.
## Setting an initial value for *Stack* field
It's now possible to add elements such as specific content items and components to the initial value of a *Stack* field.
This release also allows you to add an initial value to a [*Content reference* field](/content-modeling/field-types#content-reference-field).
With this feature, you get a suggested outline when you create more complex content items such as pages with many possible elements.
For example, when you create a new *Page* content item, you could get a preselected *Hero* component, *Feature* component and a *Call to action* item.

This allows you to create consistent and structured content, and saves you time and reduces errors.
Check out the [*Stack* field setup](/content-modeling/field-types#stack-field) for the setup details.
## Improved content item filtering
We've added the **Unpublished changes** option to the *Publication status* filter.
This addition joins the existing filters for *Published*, *Scheduled*, and *Not published* items, giving you better control and visibility over draft updates.
You can use this filter option in the list, calendar, and kanban views to streamline your content management workflow.
Check out the [content management doc](/content-management/managing-content/managing-content-items#publication-status) for more details.
## Support for dates and location fields in Algolia and Typesense integrations
The Prepr search integrations with Algolia and Typesense now support date fields and location fields. This update enables more flexible filtering options, such as showing nearby events or sorting content based on dates, making the integration more useful for time-based and location-based use cases.
Check out the [Algolia docs](/integrations/algolia) and [Typesense docs](/integrations/typesense) for setting up the integration with dates and location fields.
## Two-factor authentication (2FA) required for Owners and Admins starting July 1, 2025
To strengthen account security, 2FA will be required for all users with an *Owner* or *Admin* role in a paid Prepr organization as of July 1, 2025.
If 2FA is not enabled by that date, users will be prompted to set it up before accessing Prepr.
SSO users are exempt from this requirement.
Check out the [Activating two-factor authentication](/project-setup/managing-users#activate-two-factor-authentication) guide for more details.
## Improved document naming and URL structure
We're pleased to let you know that we've improved how document files are named and how their URLs are set in Prepr CMS.
Now when you upload new documents (pdf, zip, docx and xlsx), they retain their original file name.
The URL to download a document includes a cleaner prefix after the hostname, providing a more structured and consistent format for better organization and readability.
**Example**
Previous URL structure: `https://example.files.prepr.io/695a4d1eiaom-sustainability-report.pdf`
New URL structure: `https://example.files.prepr.io/695a4d1eiaom/sustainability-report.pdf`
These changes make it easier for you to manage these assets, better visibility, and they align with SEO best practices.
Check out the [Editing assets doc](/content-management/managing-assets/managing-assets#editing-assets) for more details on documents fields.
## Sending events to Google Tag Manager
It's now possible to send experiment-related Prepr events to Google Tag Manager (GTM).
This is a seamless integration of Prepr's experiment data with your existing GTM setup, giving you centralized tracking.
To enable the integration to GTM, simply update the *Prepr Tracking Code* in your front end to include the *googleTagManager* destination flag.
Check out the [tracking setup doc](/data-collection/setting-up-the-tracking-code#sending-events-to-google-tag-manager-gtm) for more details.
## Introducing content item calendar view
With the content item calendar view, you can now easily manage your scheduled content directly within a calendar interface.
With this clear, visual overview of all your scheduled items, it's now easier to manage timing and avoid overlaps saving time and keeping you organized.

For more details, check out the [content management doc](/content-management/managing-content/managing-content-items#calendar)
## Publication status in content item list
We’ve updated the content item list display for content reference fields, stack fields and their corresponding search dialogs to display the publication status instead of the workflow stage.
Now when you view these fields or add a content item for a content reference or stack field, you can easily see which of the listed content items are published or not.


This change helps reduce confusion, and gives you clearer and more actionable information.
Check out the [content management docs](/content-management/managing-content/managing-content-items#publication-status) for more details on the publication status.
## Setting a personal default locale
Users can now set a personal default locale that overrides the environment’s default.
This is especially useful for international teams, giving editors a more tailored experience when working with multilingual content.

Check out the [localizing content docs](/content-management/localizing-content#working-with-multiple-locales) for more details.
## New slug option to remove trailing slash automatically
We’ve added a new option to automatically remove trailing slashes from slugs when the field loses focus. A trailing slash typically indicates a directory in URLs, but inconsistent use can lead to messy or duplicate links. With this update, Prepr ensures cleaner and more consistent URLs by trimming trailing slashes, helping you maintain a more structured and SEO-friendly content setup.

Check out the [slug field settings](/content-modeling/field-types#slug-field) for more details.
## Introducing the new *Form* field
With the new *Form* field, you can now easily add *HubSpot* and *Typeform* forms directly in your content items.

With this feature, you keep your content and related embedded forms all in one place.
Just click to search for the form you need. It's that simple.
Check out the integration docs for [HubSpot](/integrations/hubspot#make-hubspot-forms-available-in-content-items) and the [Typeform](/integrations/typeform#add-form-field-to-schema) to learn how to use the [Form field](/content-modeling/field-types#form-field).
## Advanced filtering on content items
We bring you more advanced filtering on content items to help you find exactly the content items you need.
Until now, you could only filter by one value for each of the listed filter options, apart from *Tags*.
With this update you can filter by multiple values, for example when you want to view a list of both *Page* and *Post* content items, you can simply choose both values when you filter by the *Model* option.

This update ensures efficient content searches.
Check out the [content management docs](/content-management/managing-content/managing-content-items#filter-content-items) for more details on content filters.
## Help text as field value placeholder
In addition to the help text line above a field or as a tooltip next to a field name, we've added a new way to display help texts: directly in the field value as a placeholder.
Now, you can see some help text directly in the *Text*, *Slug*, *Tags*, *Number*, *Location*, and *Social* fields.

This gives you clear instructions while keeping the interface clean and intuitive.
With this update, editor guidance is always in the right place without cluttering the design.
Check out the setup details in the [field appearance settings](/content-modeling/field-types#common-settings).
## New Help text field for better editor guidance
We're happy to bring you improved guidance while editing content with the new *Help text* field.
A developer can add this field to any model or component to give you more visible and structured instructions.

With the flexibility of this feature, it ensures smoother and more efficient content management.
For more details, check out the [Help text field setup](/content-modeling/field-types#help-text-field).
## Removal of after and before parameters in REST Mutation API index requests
We’re simplifying our REST Mutation API pagination parameters. Starting **July 1, 2025**, the following changes will take effect in all index requests:
- The `after` and `before` response parameters will be removed.
- The `after` request parameter (used to skip pagination items) will also be removed.
If you’re currently using the `after/before` parameters, we recommend updating your integration to use the `skip` parameter instead.
This update improves consistency across our APIs.
## Introducing conditional fields
Introducing conditional fields - a huge step toward easier and more efficient content management.
With conditional fields you can now choose to show or hide fields or sections depending on another field's value.

This feature simplifies the editing experience with a cleaner interface without unnecessary fields.
For example, to show either the external URL field or an internal link field in the content item and not both.

Check out the [field settings doc](/content-modeling/field-types#common-settings) for more details.
## New in Visual Editing: Segment & A/B Test preview
We've enhanced [Visual Editing](/changelog#introducing-visual-editing) with new *Segment* and *A/B test* variant switches.
These switches allow you to preview personalized content for specific segments and each A/B test variant before publishing the content item.
By previewing content for specific groups of targeted visitors, you have more control and confidence in your content adjustments.

The *Segment* and *A/B test* switches are available automatically if your front end uses the [latest Prepr Next.js package](/prepr-nextjs-package).
If not, check out the [setup details](/project-setup/setting-up-previews-and-visual-editing#enable-segment-and-ab-test-switches) to enable the adaptive preview for any other front-end framework.
## Managing content with shortcut keys
In line with the [*Content UX* improvements](#updated-content-item-ux) released last week, we're happy to introduce new shortcut keys to help you manage your content faster and more efficiently.
Now, with just a couple of keystrokes, you can quickly select all content items with , add an item with , publish a content item with , and more - giving you greater control and saving you valuable time.
For more details, check out the [managing content items docs](/content-management/managing-content/managing-content-items#manage-content-items-with-shortcut-keys).
## Improved rescheduling of content items
Based on your feedback on the [recent Content UX updates](#updated-content-item-ux), we've improved how rescheduling works for published content — it now updates the *First Published at* date and time to the new date and time you choose when you reschedule the content item.

By rescheduling a published content item, you can choose how to order the content items in the front end when they're ordered by the *First Published at* date and time.
This gives you more flexibility and allows you better control over the order of published content.
Check out the [content management docs](/content-management/managing-content/managing-content-items#schedule-a-content-item) for more details.
## Introducing Visual Editing
Visual Editing is finally here! This new feature allows you to view content changes to your web pages in real-time with a convenient side-by-side view in the Prepr Content page.

This means, you can instantly see how your edits affect the page layout and content, reducing the need to switch between tabs to check your updates.
Visual Editing gives you instant, crystal-clear insight into your changes, making your editing process a lot faster, effortlessly smooth, and a great deal more intuitive.
Check out the [visual editing setup](/project-setup/setting-up-previews-and-visual-editing) and the [content management docs](/content-management/managing-content/managing-content-items) for more details.
## Improved UX for content references
In response to your feedback, we've improved the UX for content references when editing a content item.

With the updated overlays, now you always know where you are in deeply nested items.
Together with a cleaner interface, this clarity minimizes errors and gives you full control over your content, making content updates seamless and stress-free.
Check out the [content reference doc](/content-management/managing-content/creating-rich-content#adding-content-references) for more details.
## Updated content item UX
To support the *Visual Editing* feature, we've updated the *Content Item* detail page with a cleaner, more intuitive interface.
Primary content editing actions have been moved to the top.
You'll also notice that the default publish action has been updated to match your preference.

We trust this update gives you a more streamlined experience when editing content.
Check out the [content management docs](/content-management/managing-content/managing-content-items) for more details.
## Deploying to Vercel directly from Prepr
As you've requested, you can now deploy your website directly from Prepr.
Simply click the new **Build and deploy** button to deploy your website to Vercel and the live website is updated with your latest published content as soon as you need it.

This new feature is especially useful for statically built and deployed websites where you don't see your content changes immediately after publishing.
This way, you have more control over the website content without needing to switch over to Vercel or to contact developers to trigger a new deployment for you.
Check out the [Vercel integration doc](/integrations/vercel) for the setup details.
## Improved video upload feedback
We've improved the video upload experience in Prepr by adding a real-time progress indicator, showing the exact percentage of transcoding completion.
This makes it easier to track the status of larger video uploads.
Additionally, we've introduced a `Failed to Transcode` error message, providing clear feedback if an issue occurs during processing.
Check out the [assets doc](/content-management/managing-assets/managing-assets#uploading-assets) for more details.
## Improved content item creation
Based on your feedback on adding content items, we've refined the **Add item** action for usability.
Previously, when adding an item, you could select from all models when creating new content items.
From now on, the model selection window excludes single-item models if their content item has been created.
This update declutters the model selection, making content management more intuitive and efficient.
Check out the [content management](/content-management/managing-content/managing-content-items#create-a-content-item) doc for more details.
## Setting an image focal point
As requested, we’ve added a feature that allows you to set a focal point when adding or editing an image in a content item.

To enable the option to set an image focal point instead of the option to crop the image, check out the [asset field settings](/content-modeling/field-types#assets-field-settings) for more details.
By setting a focal point for an image, you ensure the key parts of the image remains visible (such as a person’s face or a product), even when resized for different screen sizes.
This feature means your front end delivers better visual presentation, and improves user experience.
Check out the [image docs](/content-management/managing-assets/editing-and-configuring-assets#setting-an-image-focal-point) for more details.
## New Marketer role
With the new *Marketer* role, you can add your marketing team members to Prepr.
The Marketer role is designed for users who manage and optimize audience segmentation within Prepr.
This role has the same permissions as the existing *Editor* role, allowing users to create, edit, and manage content.
However, marketers also have access to the **Segments** feature, enabling them to define and view audience segments.
Check out the [roles and permissions doc](/project-setup/managing-roles-and-permissions) for more details.
## Introducing the Zapier integration
We set up Prepr’s Zapier integration as part of our efforts to continually improve your experience with integrating to Prepr CMS.
This new integration enables automated tagging of customer profiles and provides an external trigger for event tracking in Prepr.
With Zapier you can choose any listed app to send data to Prepr automatically.

Think about the case where you want to add a tag with the `industry` of a known customer in Prepr when they request a demo through a HubSpot form.
Previously, you had to manually export and import this data to Prepr or create a custom API integration.
Now you can easily automate this process with [Zapier](https://zapier.com/apps/prepr/integrations) by simply choosing the listed app to pair such as **HubSpot**, a trigger like a **New Form Submission** (for example, when there's a demo request), and the Prepr action of **Tag a Customer Profile**.
This workflow in Zapier automatically triggers Prepr to save the customer data you choose in the tag of a known customer profile.
This means you have an easy, automated segmentation setup without development effort, leading to better personalization.
Check out the [Zapier integrations doc](/integrations/zapier) for more details.
## Introducing support for semantic versioning (SemVer)
As requested we've added support for semantic versioning in the *HTTP header* context in segments.
This means you can create conditions like in the example image below.

Semantic versioning gives you more flexibility and precision with your segmentation in the HTTP header context.
You can confidently enforce version requirements, preventing unwanted or inconsistent content when segmenting your customers.
Check out the [segments doc](/personalization/managing-segments#http-header) for more details.
## Bulk publishing content items
We’re very happy to bring you new options to quickly publish or unpublish multiple items simultaneously.
With these new actions you can choose to publish or unpublish your chosen content items immediately or to schedule these actions for a future date.

These new features help you efficiently release and manage batches of content items, such as campaigns or updates that span several pieces of content, saving time and reducing manual effort.
No more repetitive manual tasks means that you can manage your content faster, freeing up time to focus on strategy and creativity.
Check out the [managing content docs](/content-management/managing-content/managing-content-items#bulk-actions-on-content-items) for more details.
## Introducing the Prepr Next.js package
We’re excited to introduce the **Prepr Next.js package**, a powerful toolkit to streamline your personalization and A/B testing implementation.
Now you can integrate Prepr’s features into your Next.js front end faster and more efficiently with the following features:
- It provides API request headers for the following values:
- Each visitor's customer ID. You need this API request header, `Prepr-Customer-Id`, when you query adaptive content and content with A/B testing.
- Any UTM parameters, if applicable. This is useful to identify customers who enter your website through a social media campaign, for example.
- HubSpot cookie, if it exists. This is useful for identifying customers who are tracked in HubSpot as a lead, for example.
- The visitor's IP address. This is useful for localization.
* The *Adaptive Preview Bar*. When you include this component in your front end, you allow content editors to effortlessly toggle between A/B test variants and personalized experiences for validation.

Check out the [Prepr Next.js package guide](/prepr-nextjs-package) or visit the [GitHub repository](https://github.com/preprio/prepr-nextjs) directly for the step-by-step instructions to install and use the package.
## Tracking customers by their email address
You can now track a visitor's email address and store it in their customer profile in Prepr.
You can do this by triggering a simple javascript in your front end when the customer provides their email address in the web app.
```js copy
prepr('event', 'Email', 'jesse.ward@acme-company.com');
```
Check out the [tracking events doc](/data-collection/recording-events#Email) for more details.
This feature makes it easier to track customer interactions in other platforms you might be using, enabling more insights.
## Improved *Publication date* filter for the content item list
We're happy to bring you an improved publication date filter when you view the content items list.
The previously called *Published on* filter is now called *Publication date* for greater clarity.
This filter is based on the content item *Publication date* instead of the previous *Publish on* dates.
We've also updated the date selection for this filter to support future dates, allowing you to easily find not only past published content items, but also scheduled content items.

For more details on filtering content items, check out the [managing content doc](/content-management/managing-content/managing-content-items#filter-content-items).
## New info text to indicate null boolean values
We've added info text to indicate when a boolean value is null in a content item.
This info text indicates that you need to explicitly set a value for this field and save the content item.

## Sync your schema with Azure DevOps
We're happy to announce that we've added another option to the *Schema sync* feature in Prepr.
You can now choose to sync a schema using *Azure DevOps*.
If your preferred tool for CI/CD workflows and source control management is *Azure DevOps*, you can include the sync process to manage schema updates in this single platform.

Check out the step-by-step guide in the [Azure DevOps schema sync doc](/development/working-with-cicd/syncing-a-schema#azure-devops-schema-sync).
## Support for Stories in the REST API has been fully removed
Following the deprecation of Stories in Prepr UI in January 2024, we have now completed the removal process by eliminating all related functionality from our REST API. We trust that this update has very limited impact, if at all, but please ensure your integrations are updated, if needed. Contact Prepr support if you have any questions.
## Duplicate models, components and enumerations instantly
We're happy to bring you yet another feature you've requested - The option to duplicate models, components, and enumerations.

Quickly create similar models, components or enumerations without the need to manually add each field.
This saves you time and reduces errors by duplicating existing structures, streamlining your workflow for faster content modeling and delivery.
For more details, check out the [content modeling docs](/content-modeling/managing-models#duplicate-a-model).
## Improved image naming and URL structure
We're pleased to let you know that we've improved how image files are named and how their URLs are set in Prepr CMS.
Now when you upload new images, they retain their original file name.
The optimized URL for each image also includes a cleaner prefix after the hostname, providing a more structured and consistent format for better organization and readability.
**Example**
Previous URL structure: `https://example.stream.prepr.io/{format_options}/695a4d1eiaom-sustainability.png`
New URL structure: `https://example.stream.prepr.io/695a4d1eiaom/{format_options}/sustainability.png`
These changes make it easier for you to manage these assets, better visibility, and they align with SEO best practices.
Check out the [Editing assets doc](/content-management/managing-assets/managing-assets#editing-assets) for more details on image fields.
Source: https://docs.prepr.io/stay-updated/changelog2025
---
# Changelog 2024
Find the beautiful features and important updates that were added to Prepr in 2024.
This changelog gives you an insight into the most eye-catching releases during this period.
## Introducing new embeds for Bluesky and Threads
As requested, we're pleased to bring you two new embeds in your content: Bluesky and Threads, two rapidly growing social networking platforms.
These embeds allow you to enrich your content with real-time updates from these platforms.
By leveraging embeds from Bluesky and Threads, you keep your content relevant and make your content more appealing to web app visitors.
Check out the [creating rich content doc](/content-management/managing-content/creating-rich-content#the-dynamic-content-editor) for more details.
## Prepr now supports Vercel Content Link
We’re excited to announce that Prepr now supports *Vercel Content Link*.
This feature allows you to edit website content directly from your preview website.
By enabling *Edit Mode* via the toolbar, users can simply hover over elements to reveal links that open the corresponding item for quick updates — no developer needed.

Check out the [Previewing doc](/project-setup/setting-up-previews-and-visual-editing#activate-edit-mode-in-your-front-end) on how to activate the Content Link.
## New HubSpot integration: Adaptive content for HubSpot segments
The new HubSpot integration allows you to create segments in Prepr based on HubSpot lists.
Once this integration and relevant segment is set up, Prepr automatically adds the known HubSpot contact to the appropriate segment when they visit your website.

Your website can then render adaptive content for this visitor. Check out the [segments docs](/personalization/managing-segments) for more details.
This integration is useful when HubSpot is a source for your segments, such as for specific campaigns or segments based on leads.
This means you can seamlessly leverage these existing segments to deliver adaptive content directly in your website.
Enhance your engagement strategy by providing adaptive content for contacts from HubSpot campaigns.
Check out the [HubSpot doc](/integrations/hubspot) on how to activate the integration.
## Adding time filters to event conditions in a segment
As part of our ongoing efforts to enhance the adaptive content features, we've extended the segment event conditions with a time filter.
Now you can add a time filter when adding an event condition to target customers who interacted with specific pages.
For example, to segment customers who visited a landing page in the last month.

This precise segmentation allows you to identify and engage with customers based on their recent activity or interest.
By leveraging these insights, you can improve engagement and conversions by targeting the right audience at the right time.
Check out the [segments doc](/personalization/managing-segments#time-filter) for more details.
## New Day and Time context filters in segment designer
We're happy to announce an extension to the segment designer context selection.
You can now choose one or more days or times when customers visit your website.

Creating segments with these filters allow you to target customers with special time-specific or day-specific content such as discounts/promotions.
For example, offering discounts to customers who make purchases on the weekend.
Check out the [Segments doc](/personalization/managing-segments) for more details.
## Improved media browser
We’re happy to introduce a new workflow for adding assets to content items with an improved media browser.
When you add assets to content items, you now have the option to drag and drop an asset directly into your content item.

Other than the clearer asset upload options, you'll notice more intuitive image cropping.
This streamlined process allows you to manage assets directly while editing your content, making everything faster and easier.
We've cut out all the extra clicks, letting you focus more on your core tasks to create impactful content.
Check out the [assets doc](/content-management/managing-assets/editing-and-configuring-assets) on how to edit and configure assets in your content items.
## New GraphQL API version 2024-12-05 is available
The GraphQL API has been updated with the following additions:
- There is a new default `_json` field in the Remote Source type that contains the raw data content for the remote source.
- The API now supports single-item Remote source fields.
- It also supports two new embed types, `BlueskyPost` and `ThreadsPost`.
- The API allows you to filter *Stack* fields in a model by the *Typename* of a component in the stack.
Check out the [GraphQL API upgrade guide](/graphql-api/upgrade-guide#version-2024-12-05) for more details.
## New HTTP header context in the segment designer
Once again, we bring you an update based on your feedback: The new *HTTP header* context option in the segment designer.
You can now segment customers based on a context related to the web app that customers use.
For example, to display specific content to customers based on the app version they're using.
This means you can deliver precise content, such as version-specific content, to customer segments, supporting seamless feature rollouts or phased updates.

Check out the [segment designer docs](/personalization/managing-segments) for more details.
## Update existing content items when adding fields
We're excited to bring you a new option to streamline your workflow when adding new fields.
Now, when you add a *Text*, *Number*, *Boolean*, or *List* field to a model and set the **Initial value**, Prepr allows you to update all existing content items with one click.

You no longer need backend scripts to bulk update the related content items when adding new fields to a model.
This feature not only saves you time but reduces the risk of missing content and potential site issues.
For more details, check out the [field types doc](/content-modeling/field-types#text-field).
## Reassigning a duplicate content item
As requested, we've made creating similar content items even more efficient.
From now on, when you duplicate a content item, it's automatically assigned to you.
This also means no more unnecessary notifications for others.
For more details on editing content check out the [Managing content items docs](/content-management/managing-content/managing-content-items).
## New Segment Designer
We’re thrilled to introduce the new *Segment Designer*, a major product update shaped by your feedback on the Prepr personalization feature.
The *Segment Designer* helps you segment your audience with greater precision, improving visitor experiences that lead to higher engagement and conversions.

Now when you create customer segments, you can set up more precise conditions, and define a current context for the segment.
The *Context* of a segment includes current info about the customer, like the device they're using or the country they're in, when they interact with personalized content.
To align with the new *Segment Designer*, we've made some minor updates when you add *Adaptive content* to a content item.
The *Country* selection based on a visitor's geolocation has been removed. You can now set this up in the *Context* of the segment instead.
These changes ensure that your segments in the Adaptive content are consistent with the segments that you've built.
Let's take a look at new features in more detail:
- Logical operators
Previously, multiple conditions in the same segment were processed as mutually inclusive (AND), so it was more difficult to set up independent conditions for the same segment.
Now you can explicitly choose the logical operators `AND` or `OR` to combine conditions exactly the way you need them.
- Streamlined UI for easier segmentation
The *Segment Designer* has a more intuitive design that makes it easier for you to build segments.
As you set up conditions, they now form clear, logical sentences making it easier to see exactly which groups of customers are included in your segments.
- New filter options
- *Event frequency:* Segment customers who, for example, viewed a page more than three times.
- *Event with content reference:* Target customers based on a referenced content item, such as an article by a particular author.
- *Event for specific models:* Create segments for customers who viewed types of content like blog articles.
- *Previous session:* Segment based on customers who last visited within a specific time frame, like the past 30 days.
We're confident that this update makes segmenting your audience much simpler and more intuitive, while giving you the option to be more precise with your personalization.
For more details on how to build customer segments, check out the [Segments doc](/personalization/managing-segments).
If you have more suggestions on how we can simplify your experience with Prepr, [we'd love to hear from you.](https://docs.google.com/forms/d/e/1FAIpQLSf2kANsW0MRMOsETQVE5Ac6ikVyJqz7Zi7yes86aKaC9oFf5w/viewform?usp=pp_url)
## New visual model and component selection
Great news for content editors! The new visual model and component selection solves the following challenges when adding content to your web pages:
1. Many components have technical names, like a *Call to action* component.
2. The list of content items and components is often very long for you to scroll through.
Now when you choose a content item or component in a stack or reference field, you'll see a visual preview of what your content item or component could look like.
You can also easily find content items or components by a tag which shows you a logical grouping, such as *Articles*.

We trust that you'll enjoy this more intuitive selector that boosts productivity when editing content.
To make the preview images and tags available, developers can upload preview images and define relevant tags, where needed, directly in the model or component settings.
Check out the [model settings](/content-modeling/managing-models#appearance) or [component settings](/content-modeling/managing-components#appearance) for more details.
## New dwell time metric
We are excited to introduce the new dwell time metric for your A/B tests and adaptive content.
Until now, you could only track their results with surface-level metrics such as the number of impressions and conversions.
While these metrics provide good insights into visitor behavior, in some cases you need a better insight into user engagement of some elements on a page, especially when you're not tracking any clicks.
With the new dwell time metric, you can track the average amount of time that customers view a specific element on a page, giving you this deeper insight.
When used in A/B testing, dwell time allows you to compare the effectiveness of different versions of content.
For example, if variant A of a *Product description* holds the user’s attention for an average of 45 seconds, while version B only captures 20 seconds, it's obvious that variant A is more effective.

Check out the [A/B testing guide](/ab-testing/running-ab-tests#evaluate-the-ab-test) or the [Adaptive content guide](/personalization/managing-adaptive-content#evaluate-the-personalized-variants) for more details.
## New GraphQL API version 2024-10-04 is available
The GraphQL API has been updated to support the single-item [*Stack*](/graphql-api/schema-field-types#fetching-a-single-stack-field) and [*Content reference*](/graphql-api/schema-field-types#fetching-a-single-content-reference-field) fields.
It also includes a change to retrieving an A/B test with only an A variant.
A/B tests without a B variant will now return no element for B targeted visitors, instead of defaulting to A.
Check out the [GraphQL API upgrade guide](/graphql-api/upgrade-guide) for more details.
## Filter assets by enumeration values
You can now filter your assets by specific enumeration values, making it even easier to find the ones you need.
For example, filter assets by an enumeration field *License holder* to find videos or images that belong to certain companies.

This enhanced filtering allows you to quickly and easily locate the assets you need, boosting your productivity.
Check out the [managing assets doc](/content-management/managing-assets/managing-assets#finding-assets) for more details.
## Choosing a component title
Normally, the title of a component in a *Stack* or *Dynamic content* field is set to the first text element in your component.
But, based on your feedback, we’ve made things a bit more flexible.
At times, you may want the component title to be the title of a referenced content item.
For example, when the embedded component title should be the headline of an article.
Also, a component might just have a *List* or *Number* field, and in those cases, you might want the component title to be the chosen list item value or the number entered.
For example, a number of the item that defines its order in a list.

As a developer, you now have the option to set the component title to these values instead, giving you more control and flexibility.
This means the content editor sees clearer and more meaningful component titles in their content, making their content more understandable at a single glance.
Check out the [components doc](/content-modeling/managing-components) for more details.
## Single content reference field and single stack field
As a developer, you can now configure a single content reference field and a single stack field.
This setting limits the content editor to adding just one referenced content item to a content reference field
and just one referenced content item or component to a stack field.
For example: If an article should only have one author.

If you change a content reference or stack field to a single type, you'll get a warning to change existing queries in your front end.
The single, flat structure reduces data complexity, making API queries faster and simpler.
This means improved performance and quicker data retrieval, so you can focus on building with less overhead.
Check out the [API field types doc](/content-modeling/field-types#content-reference-field) for more details.
## Choosing your own conversion event
Until now, your metrics data for *Adaptive content* and *A/B testing* was based on the `click` event.
With this update, you can now choose the event which represents conversions for you.
Check out the [A/B testing doc](/ab-testing/setting-up-ab-testing#track-impressions-and-conversions-for-stack-field-ab-test) on how to send your conversion event to Prepr, such as a custom event for quote requests.
With the new event filter in the metrics modal, you can choose to view metrics by the conversion event that matters to you.

This means you’ll be making optimization decisions based on even more precise and relevant data, helping you drive better results.
## Introducing Bitbucket Schema Sync
In addition to the *Direct*, *GitHub*, and *GitLab Schema Sync* options, you can now choose to sync the schema using Bitbucket.
If your preferred tool is Bitbucket, you have control over the sync process to manage schema updates exactly the way you need.

Check out more details in the [syncing a schema doc](/development/working-with-cicd/syncing-a-schema).
## New Technical contact role
With the new *Technical contact* role, you can add your technical admin team members to Prepr.
They can then be contacted directly for any incidents related to failed webhooks, or remote sources that are not syncing.
Any announcements from the Prepr status page will also be emailed to them automatically.
This means a quicker collaboration and response to solving incidents.
Check out the [roles and permissions doc](/project-setup/managing-roles-and-permissions) for more details.
## New help text display option
We’ve added a new way to show help text in content items.
Instead of displaying the help text in small font below each field, you can now have it appear as a tooltip when editors hover over the icon next to the field name.

This option makes longer help text more readable and keeps content items uncluttered.
Content editors can quickly check the help text when they need it, without it getting in the way.
## Overview of linked content items
We've added a new sidebar option to the content item detail page that shows where this content item is referenced.
You can now easily find and view linked content items.
When you try to delete a content item that is referenced in other content items, you'll get a warning about the linked items.
This means you can avoid broken pages when deleting a content item with linked items. For example, find all articles linked to a specific author from their detail page.

## New content view: Adaptive content and A/B tests
With more users adding A/B tests and adaptive content in Prepr, and thanks to your valuable feedback, we've introduced a brand-new view to the content items overview page. Now, you can easily see all content items with adaptive content and A/B tests at a glance.
It’s faster and simpler to find exactly what you're looking for.
You can also filter this view further by the **Status** (*Active* or *Inactive*), **Type** (*Adaptive content* or *A/B test*), the content item **Model**, and the **Language**.

Check out the [content items doc](/content-management/managing-content/managing-content-items#content-views) for more details.
## Block AI bots from scraping assets
We've implemented an update to block all AI bots from scraping your video and document files from Prepr *Media*. This reduces the amount of bandwith you use.
If you want to enable access to that content for those bots, ask the Prepr account owner to [contact our Support Team](https://prepr.io/support) to enable this.
## New API option to open files in the browser
When you request files in a GraphQL API query, they are downloaded by default.
With this update, you can now include a URL argument, `inline` with a value of `true` to display the file contents in a browser instead.
This means that you get the file exactly the way you need to deliver it to your visitors.
Check out the [GraphQL docs](/graphql-api/schema-field-types#files) for more details.
## Organize your enumerations into schema folders
It's now possible to organize your enumerations into folders like you can with models and components.
When you have dozens of enumerations, these folders make it easier to find related enumerations instead of scanning a long alphabetical list.
For example, when you have multiple enumerations needed for the same components.

Check out the [enumerations doc](/content-modeling/managing-enumerations#organize-enumerations-into-folders) for more details.
## Exclude IP addresses from data collection
We've added a feature in the event tracking settings that lets you exclude specific IP addresses from data collection. This helps you maintain the integrity of your analytics by ensuring that actions like impressions and clicks from those IPs are not tracked. By filtering out internal traffic, you can keep your data clean, which is crucial for accurate AB testing and decision-making.

Check out the [collect event data docs](/data-collection/setting-up-the-tracking-code#excluding-ip-addresses) for more details.
## Filter content items by enumeration values
You can now filter your content items by specific enumeration values, making it even easier to find the items you need.
For example, filter *Product* content items by the *Size* to find large T-shirts.

This enhanced filtering allows you to quickly and easily locate the content you need, boosting your productivity.
Check out the [filter content items doc](/content-management/managing-content/managing-content-items#filter-content-items) for more details.
## Schema sync upgrade
We've upgraded the *Schema Sync* and added the *GitLab Schema Sync*.
With the improvements to the Schema Sync process, you'll see better error handling and can now preview changes before completing the sync.
In addition to the *Direct Schema Sync* and the *GitHub Schema Sync* options, you can now choose to sync the schema using GitLab.
This upgrade gives you more control over the sync process to manage schema updates exactly the way you need.

Check out the [Schema sync doc](/development/working-with-cicd/syncing-a-schema) for more details.
## Improved schema import and export processes
As part of our continued efforts to improve existing Prepr features, we've updated the import and export of models and components.
The import and export logic has been updated to match the *Direct Schema sync* process to ensure consistency of models and components during the individual import/export processes.
In addition to this improvement, it's now also possible to import and export individual enumerations and remote sources.
When you only want one or a couple of enumerations or remote sources created in a test environment then you no longer have to create them manually.
The improved import and export keeps your data structure intact.
This means your schema integrity is maintained even when importing complex models or components with embedded elements and reference fields.
This, in turn, means more accurate testing. For more details, check out the [Schema docs](/content-modeling/managing-models#export-and-import-a-model).
## User management updates
We've made some improvements to the user management feature. In particular, the following updates are now available for Agency users:
- 2FA is now required for added security when managing client environments.
- Agencies now have to manage permissions for agency users at agency account level. It’s no longer possible to manage agency users at the organization or environment level.
- Agencies can now create their own custom roles that can be used in all client environments.
- Permission to view the audit logs are now available to all agency accounts.
The updated user management improves control for agency users, provides better oversight and gives more flexibility when managing their client users and permissions.
Check out the [manage users doc](/project-setup/managing-users#agency-accounts) for more details.
## Introducing Metrics for A/B Testing and Personalization
Prepr is a headless CMS with A/B testing and personalization features. Until now, it was only possible to measure A/B testing and personalization in external analytics tools.
Based on feedback from our customers, we've implemented the option to track impressions and conversions to help you determine the results of your optimizations.
Now you can see these results directly in Prepr.
This means you gain insights into the impact of your experiments quickly and easily without needing other analytics tools. You can then use these insights to continuously improve customer satisfaction and conversion rates. Moving from biased design decisions to fact-based design decisions.
It's now possible to set up your front end to send data to Prepr for metrics.
For more details on how to set up your front end to trigger metrics calculations in Prepr, check out the [A/B testing doc](/ab-testing/setting-up-ab-testing)
or the [Personalization doc](/personalization/setting-up-personalization).
When metrics data is available you can view the results from your A/B test or personalization group.
You can then filter the results by a chosen date range (defaulted to the last 90 days)
or by a chosen segment in the case of A/B testing. The following metrics data is available:
- Number of impressions
- Number of conversions
- Conversion rate
- Standard error
- Uplift
- Probability that a particular variant performs better than the control.
- Simple graph view of results

For details on how to intepret the metrics, check out the [Run A/B tests doc](/ab-testing/running-ab-tests) or the [Personalize website doc](/personalization/managing-adaptive-content).
The Metrics feature can be used successfully from GraphQL API Version 2023-04-17. If you have an older GraphQL API version, check out the [GraphQL API upgrade guide](/graphql-api/upgrade-guide#how-to-upgrade-to-a-newer-graphql-api-version).
If you have more suggestions on how we can simplify your experience with Prepr, [we'd love to hear them!](https://docs.google.com/forms/d/e/1FAIpQLSf2kANsW0MRMOsETQVE5Ac6ikVyJqz7Zi7yes86aKaC9oFf5w/viewform?usp=pp_url)
## New Settings for A/B Testing and Personalization
We're happy to announce new settings available for your A/B tests and personalization.
Previously, each A/B test variant was shown to 50% of customers.
It's now possible to modify the percentage of traffic allocated to each variant during an A/B test.
This means you can optimize your experiments for more accurate results. So, it ensures faster decision-making.
Check out how to manage this setting in the [A/B testing guide](/ab-testing/running-ab-tests#manage-the-ab-test).

Previously, a query for a content item with personalization only returned the first matched personalized element.
It's now possible to enable the API to return all the matching personalized elements instead.
For example, when there are multiple FAQ items that include the same segment.
This allows you to choose which personalized elements are shown in the front end, meaning more relevant content.
Check out how to manage this setting in the [Personalization guide](/personalization/managing-adaptive-content#manage-the-personalization).

## New SEO field options for components
Previously, you could only set model text fields as SEO titles or meta descriptions.
Now, you can mark text fields in components as SEO titles or meta descriptions, like you can in models.
This means you can manage your SEO attributes consistently across your schema.
In so doing, you improve the visibility and ranking of your content more efficiently, leading to better search engine performance and increased web traffic.

Check out the [Text field doc](/content-modeling/field-types#item-title-and-seo-fields) for more details.
## Disable content item deletion
Based on your feedback, we've added a setting that disables content item deletion for a model. When active, this setting prevents editors accidentally deleting content items.
Now you don’t have to worry about content editors removing content items that store important system information, such as *App configuration* like global meta tags and copyright info.

This update makes working with Prepr even smoother. If you have more feedback on how we can simplify your experience with Prepr, [we'd love to hear from you!](https://docs.google.com/forms/d/e/1FAIpQLSf2kANsW0MRMOsETQVE5Ac6ikVyJqz7Zi7yes86aKaC9oFf5w/viewform?usp=sf_link)
## New Billing role
A new default user role, Billing, is now available. This role grants access to the organization's plan and billing information, enabling users to manage payment details, view and handle invoices, and oversee subscription details. Billing users do not have access to other administrative or content-related functions within the environments or organization.
For more information on the new Billing role and its capabilities, please refer to our [updated documentation](/project-setup/managing-roles-and-permissions).
## Improved access token management
As requested, we've made a couple of updates to improve the management of access tokens.
It's usually very useful to open the *API Explorer* directly from a content item to perform GraphQL queries.
By default, this API Explorer connects to the first access token when the environment was created.
Previously, you couldn't change this default access token.
With these new UI updates, you can now switch the default access token for the **Open API Explorer** button.
Also, now you can just hover over each access token to **Edit**, **Delete**, or **Use for API Explorer**.

This improvement makes managing access tokens more intuitive and efficient.
Switching access tokens ensures the API Explorer uses the most up-to-date token.
These updates give you more flexibility and control and managing access tokens is now easier and more user-friendly.
## Improving environment stage visibility
Some of our customers manage multiple Prepr environments. In this situation, there are times when users accidentally make updates in the wrong environment.
That is why we've made minor UI updates to make it clearer to users which environment they're currently working in.
We've updated the environment drop-down selector and added a new label at the top of the screen to indicate which non-production environment you're currently working in.
This helps users who frequently swap between staging and production environments and reduces the risk of making updates in the wrong environment.

## Environment-to-environment Content Export
We are very proud to bring you the most requested feature by developers, the *Environment-to-environment Content Export*.
With the *Environment-to-Environment Content Export*, you can easily copy content across different environments.
This streamlined process reduces your dependence on Prepr support, significantly saving time.
By maintaining current content for development and testing, you improve collaboration with marketers and editors for
more accurate testing of realistic scenarios and ensure superior quality assurance, ultimately accelerating your project timelines.
The *Environment-to-environment Content Export* goes hand-in-hand with the *Schema* sync process which should be run first to make sure that content is exported to a valid schema.
Check out the [Schema sync docs](/development/working-with-cicd/syncing-a-schema) for more details.
As a developer, you can select specific content items you want to copy over to another environment.
Trigger the content export with the **Export to** action available in the Content item list page.
You can copy content between any two environments you have access to in the same organization.
Check out the [Export content doc](/development/working-with-cicd/syncing-content) for more details on the process and troubleshooting errors.

## Introducing sharing of filtered lists with URL
Sometimes, you may want to share a filtered list of content items with a team member.
However, this was previously not possible because the URL did not contain the filter parameters.
We've now added these parameters. This means you can simply copy and share the URL with your team.
This small enhancement makes collaboration easier.
## Introducing collapsible components in the Dynamic Content field
We have great news for those who use components in the Dynamic Content field.
It is now possible to collapse individual or all components in dynamic content fields in your content,
reducing clutter and providing a clearer overview.
Previously a dynamic content field with a lot of components could become chaotic to manage.
Now it's easier for you to manage and focus on the specific content that needs editing,
enhancing your workflow efficiency and making the editing process more intuitive.

## Added Text Filter Option to Remote Source
Upon customer request, we've added a text (`string`) filter to the list of filter options in a custom remote source.
You can now set this filter in the custom remote source feed and it'll become visible when an editor adds a remote item, allowing them to filter by text. For example, by a category name.
Check out the [custom remote source doc](/content-modeling/creating-a-custom-remote-source#step-1-set-up-your-custom-api-endpoint) for more details on how to set up an endpoint with filters.
## New heading display options in HTML fields
Today, we bring you new heading display options for HTML fields.
These new options allow you to configure display options for the HTML text field headings,
by giving you the flexibility to choose which headings (`H1` to `H6`) are available to editors.

Previously, all heading options were available to editors which sometimes caused styling inconsistencies in the front end.
For example, now you can ensure that key headings, like `H1`, are reserved exclusively for article titles.
By providing this level of control, this setting supports the alignment of content with the website's design and editorial standards,
enabling more effective content management.
Check out the [Text field doc](/content-modeling/field-types#text-field) for more details.
## New component display option
Based on the results from our user research, we've implemented new component options that define how component fields are displayed in a content item or in another component. You can now choose to display component fields grouped or ungrouped in content items.

Previously, the display of component fields were always grouped with the name of the component at the top of the group. For some components, it's cleaner and avoids confusion by displaying the fields like any other content item field.
Now you can display component fields seamlessly with other content item fields. For example: An *Image + Caption* component that has an image field and caption field.

However, an SEO component is a good example of where grouping all SEO-related fields is sensible, to differentiate them from other fields in the content item.
Check out the [Component field doc](/content-modeling/field-types#component-field) for more details.
## Content item search improvement
We've implemented a change to the content item *Search* functionality.
Previously, the content item search scanned through all the text in each content item, by default.
With this change, it now does a narrowed down search on the content item *Title* by default.
This improves the quality of the search results and the performance of the search when you have thousands of content items.
It's still possible to search through the *full-text* of content items by explicitly choosing this option when you click on the search bar.
The same goes for searching on the *Slug* or *ID* fields of the content items.

## Introducing Custom Events
Based on user research and your feedback on segmentation, we've implemented *Custom Events*.
Previously you could only use, capture, or track predefined events in Prepr which limited your options
and didn’t always align with your segmentation needs.
Now you can define custom events so you're completely free to set up and track the events you need.

We've also streamlined the predefined events to the list below.
- The `View` event is automatically sent to Prepr when a page load is detected by the Prepr tracking pixel.
- The `Click` event is available for an upcoming feature to create metrics for A/B testing and personalization.
- The `Like`, `Bookmark` and `Subscribe` events have built-in constraints. These events can only be recorded once per customer per content item.
- The `SignUp` event is the only event not linked to a specific content item. This event prevents the customer from being automatically deleted after 90 days of inactivity.
We trust that the custom events and predefined events cover all your needs to easily track visitors.
For details on how to track and send events to Prepr, check out the [Events doc](/data-collection/recording-events#recording-custom-events).
## New GraphQL API version 2024-06-12 is available
The GraphQL API has been updated for custom events and in preparation for upcoming features:
- A new field `variant_key` to allow you to track impressions and clicks for Personalized and A/B tested components.
This key supports the upcoming feature to provide metrics for A/B testing and personalization.
- A new system field `_event` has been added to the schema. This field along with some other minor changes to the API support *Custom Events*.
The new release also includes the following changes:
- `ENUM` types can now be set to legacy mode and will be returned as a `STRING` (for existing Prepr environments).
- Improved errors for incorrect requested locales.
Check out the [GraphQL API upgrade guide](https://docs.prepr.io/graphql-api/upgrade-guide#version-2024-06-12) for more details.
## Content item list improvements
Based on findings from our user research, we've implemented some improvements to the *Content items* list page that significantly enhance your experience with Prepr. Overall, we've updated the UI design of the *Content items* list page to make it more intuitive and less cluttered for content editors.
As part of these UI updates, you'll notice that the checkboxes in the first column are visible when you hover over a content item. Click the checkbox to select content items to perform bulk actions like **Delete** or **Assign to**. This makes it easier for editors who need to perform actions on multiple items.

When the editor clears the selection with the new **Clear selection** button, the column names will become visible again. In this view you can see the intuitive sorting in the *Content item Title* and the *Modified date* column headers.
Simply click the name of the column header or the arrows to toggle between ascending and descending order.
Click the icon to choose other sorting options for *Publication date*, *Scheduled date* and the *Created on* date.
You will also notice that we've made the sidebar collapsible to hide filters and avoid confusion. This means the editor can now focus on their core tasks, in other words, editing content items.

Last, but not least, we've made the hover interactive for actions per content item when you need them. Instead of clicking, a simple hover over the content item makes the actions you need available, like the **Edit** or **Delete**.

While the actions and sorting capabilities have always been available, the new UI changes make the editing experience a lot more intuitive and user friendly. Check out the [Manage content docs](/content-management/managing-content/managing-content-items) for more details.
If you have more suggestions on how we can simplify your experience with Prepr, we'd love to hear them!
## Content item workflow improvements
Based on user research and your feedback, we've implemented some small improvements to content items that significantly enhance your experience with Prepr.
The most obvious change is the introduction of additional publishing options. Instead of just **Publish and close**, you can now select **Publish and stay** or **Publish and add new**. This simplifies the process of publishing your work, then continuing with the same content item or immediately starting a new one.
We've also added a convenient button at the top of a content item. This lets you cancel editing without having to scroll down to find the cancel button.

Another improvement is the clear option to remove a schedule. While this was possible before, it wasn't very obvious. We've now added a link that allows you to easily remove the schedule.

Finally, you no longer need to publish parent content items when a child item is changed. Previously, changing a linked item required you to re-publish the parent item, even if it wasn't altered. This led to unnecessary extra clicks. Now, you only need to publish the changed item and can immediately continue with other tasks.
These updates have made working with Prepr even easier. If you have more suggestions on how we can simplify your experience with Prepr, we'd love to hear them!
Check out the [Manage content docs](/content-management/managing-content/managing-content-items) for more details.
## Define your own fields for *Assets*
We are very excited to bring you the long-awaited feature to add fields to Assets. It is now possible to define your own fields to keep track of asset-specific info like *Copyright* or *Source* in addition to the core asset fields for the title, description and the author.
Now, it's also possible to enable localization for your assets. This means your editors can enter information about assets in the locale that is relevant for them. This feature gives you the flexibility to create your own Asset content structure with localization and makes it much easier for the front end to retrieve additional information about assets.
This feature introduces a new **Asset** model available to any user who has access to the Prepr *Schema* or *Shared schema*.

When you enable localization on the **Asset** model, the fields in each asset will be available for all [available locales](/content-management/localizing-content).
Check out the [Asset model doc](/content-modeling/defining-the-asset-model) for more details.
## Organize your models and components with Schema folders
It's now possible to organize your models and components into folders. When you have dozens of models or components, these folders make it easier to find related models or related components instead of scanning a long alphabetical list. For example, when you have multiple components which are used as section of a page.

## Speed up text editing with new AI Generate and AI Optimize features
We are very pleased to bring you two new features, *AI Generate* and *AI Optimize*.
The *AI Generate* feature generates new text on request, for example, when a content editor wants to create a summary based on the main body text of an article. *AI Optimize* helps content editors to improve existing text, for example, to make their text longer, shorter or simpler. Both features make it quicker and easier for content editors to create more engaging text in their content.

Go to the **Schema** tab to set up the AI parameters for these features in the relevant *Text* fields. Check out the [*Text field* settings](/content-modeling/field-types#text-field) for more details.
## Use Frontify brand assets in Prepr content
As requested, we've added a DAM integration to Prepr CMS to allow content editors to access Frontify brand assets in Prepr content. This integration gives you the benefit of ensuring that your assets are brand-compliant and allows you to maintain a single source for these assets.
With this built-in integration, it's easy to set up a connection between Prepr CMS and your Frontify account. When your request for activation has been approved, you can activate the *Frontify* app in Prepr.
Once done, content editors can include Frontify assets in their content items.

For detailed instructions on integrating Frontify, please refer to the [Frontify integration guide](/integrations/frontify).
## New locales added for `fy-NL` and `es-MX`
As requested, we've added support for the Frysk `fy-NL` and Mexican Spanish `es-MX` locales. Locales can be set up in the Organization **Settings** page. This feature is available to users who have the `Owner` role or a role with the *General* `Locales` setting enabled.
For more details, check out the [localization doc](/content-management/localizing-content).
## Create cleaner schemas with Enumerations
We are excited to bring you the long-awaited *Enumerations* feature. We've expanded the *Schema* so you can define your own enumerations and use them in any model or component with the *List* field. For example, when you want to define days of the week.
Until now, you had to define list values every time you added a *List* field to a model or component. Now, you only need to create an enumeration once with a set list of values and reuse this list in multiple models or components. This means that you define a cleaner schema without duplicate list definitions.

Check out the [enumerations doc](/content-modeling/managing-enumerations) for more details.
The enumerations feature is available as from GraphQL API Version 2024-03-26. To activate and use enumerations, check out the [GraphQL API upgrade guide](/graphql-api/upgrade-guide).
## Sync your schemas with GitHub for a seamless CI/CD workflow
We are happy to bring you a new feature, the **GitHub sync**. Until now you could only use the **Sync schema** feature to sync one schema to another, but it's not always ideal.
The **GitHub sync** allows you to use standard GitHub functionality to sync schemas between environments. As a developer, this gives you more control over the sync process to manage schema updates exactly the way you need to.
- You can sync both ways with the GitHub pull and push requests.
- This sync not only adds items in the schema, but also removes unneeded items.
You benefit from *version control* in GitHub, namely:
- You can review the schema change history.
- And you can revert changes.
Check out the [Sync schemas doc](/development/working-with-cicd/syncing-a-schema#sync-schema-with-github) for more details on how to Sync your schemas with GitHub.

## Keep track of user activities with the new Audit log
With the new *Audit log* feature, it is now possible to keep track of user activities. The *Audit log* helps developers to troubleshoot errors or inconsistencies with the schema or content. The audit log is available at the organization level and you can filter logged activities by the **Date**, **Environment**, **User** and **Resource type**.
The audit log shows you *Create*, *Update* and *Delete* actions on content items, models, components, remote sources, enumerations, webhooks, apps, users, assets, roles and segments. You can also track when a user publishes a content item and when a user executes a schema sync or GitHub push/pull sync.

Check out the [Audit log doc](/project-setup/audit-log) for more details.
## New Stage option available to create a Testing environment
In our efforts to support the CI/CD process of your development team, you can now choose a **Testing** stage option when creating a new environment. This will help you adhere to best practices when managing your *Development*, *Testing*, *Acceptance* and *Production* Prepr environments. Check out [DTAP configuration options](/project-setup/setting-up-environments#set-up-a-dtap-configuration) for more details.
## Set the GraphQL API version in code
When we make changes to the GraphQL API that aren't compatible with older versions, we release a new one with a specific date. By default, the API uses the default version associated with your access token. However, in response to customer feedback, you can now override this version using the 'Prepr-Version' header. For more information, refer to our [Versioning & Upgrade Guide](/graphql-api/upgrade-guide).
## More flexibility with new remote source features
We bring you new remote source features in our ongoing endeavor to advance our existing functionality. The new features listed below give developers more control to manage remote sources more flexibly.
- **Filters**
Now you can enable filters for content editors to find remote items easier like in the example below.

Check out [the custom remote source doc](/content-modeling/creating-a-custom-remote-source#step-1-set-up-your-custom-api-endpoint) for more details on how to set up an endpoint with filters.
- **New remote source actions**
Go to the remote source and click the icon to see the new remote source actions.

- **Resync remote items**
As a developer, you can now manually resync the items from a remote source. Prepr automatically resyncs remote source items when the `changed_on` date has been modified, but there are times when you need to resync the items on demand. This way, Prepr CMS is updated and the front end is up to date with current remote items.
- **Activate**
It is now possible to activate the remote source. This is useful when the API endpoint URL is no longer valid or the external source system is updated and no longer matches the endpoint definition. To resolve errors in these situations, the remote source will be deactivated until the issues are fixed. Once fixed, click **Activate** to reactivate the remote source.
- **New remote source field settings**
- **Set as Title** and **Set as Image**
It is now possible to specify the `Title` and the `Image` when defining the fields in your remote source. Prepr uses these settings to display the Title and image for content editors to identify items from the remote source more easily.
- **Make visible when searching for remote content**
You can also choose which of the fields in the endpoint should be made `Visible` to the content editor. In the field settings, open the **Appearance** tab to enable or disable this setting.

## Add Stack to the Dynamic Content field
As you've requested, we released a new feature that allows you to add a *Stack* to the *Dynamic content* field. This new feature is an extension to the recently released [*Stack in component*](#create-better-content-structures-with-stack-in-a-component). It enables editors to create structured lists in articles that are created with the Dynamic Content field. For example, to add a list of recipes to an article about cooking equipment.
Simply enable components in the list of content types in the *Dynamic content* field and choose the components that contain a *Stack* field.

By doing this, you allow the content editor to add a component with a *Stack* field in their dynamic content. For example, they can easily add things like a list of articles to their rich text like in the image below.

## Make the most out of components by including a date field
We are pleased to announce that you can now include a date field in components. Enjoy this flexibility when modeling your content, for example, in an *Event* component where the event date is an integral part of this type of content.
The date field in a component works the same way it does in a model, with the following options:
- Single or multiple Date values
- Single or multiple Date range values
- Single or multiple Date time values
- Single or multiple Date time range values
- Business hours
Check out the [Date field](/content-modeling/field-types#date-and-time-field) for details on how to set up this field.
For developers who need to make API requests, the date field in a component is available as of GraphQL version 2024-01-31.
## More accessible content with 27 new Arabic locales
As requested, we've expanded the range of locales with Arabic languages. We've included 27 new countries in the list of locales, such as Egypt `ar-EG` and Morocco `ar-MA`. If you have international partnerships and customers, make your content more accessible and marketable by publishing content in their local language.
Check out our [localization docs](/content-management/localizing-content) to learn how to work with localization.

If you need a specific locale that is not listed, please [contact our Support Team](https://prepr.io/support), and we will add it upon request.
## Create better content structures with Stack in a component
The long awaited *Stack in a component* feature has been released. As a developer, you can now add a Stack field to components. This means that you can create more logical, flexible, and scalable content structures for your component-based pages.
A typical example is when you need a row of buttons in a Call to action. To do this, create a *Button row* component and add a stack field to it to hold the buttons. You can then add the button row to any component you need like a *Call to action*.
[Watch the video on how to use Stack in components.](https://youtu.be/SOdw6GM0wRA)

## Enjoy a renewed Dynamic Content Editor and Content Item detail page
We are happy to enter the new year with this release which promises to deliver a multitude of benefits to you as a content editor:
We implemented a new front-end framework in preparation for an upcoming release of the Live preview feature. You can now enjoy faster page loads with this update and a consistent layout when editing a content item with reference or stack fields. See below for an example of the renewed look and feel of the Personalization and A/B testing blocks in a **stack** field:

This release also includes some reworked code resulting in faster loads and an improvement to the word counter and SEO information. The deprecated **Preview** button has also been removed, but check out [the content item preview doc](/project-setup/setting-up-previews-and-visual-editing) for details on how to set up your own preview URLs.
A new *Dynamic Content Editor* has been implemented to give you a better user experience when editing lots of content at once. It's based on an established API that resolves some ongoing issues. You will notice clearer validations in the dynamic content fields, an improvement to the copy-paste function within content items and from external sources to content items, and the ability to use the Undo and Redo options.

## Make the most out of your components with the location field
We are pleased to announce that you can now include the location field in components. Enjoy this flexibility when modeling your content, for example, in an *Event* component where the event location is essential for this type of content.
The location field in a component works the same way it does in a model.
For developers who need to make API requests, the location field in a component is available as of [*GraphQL version 2023-11-02*](/graphql-api/upgrade-guide#version-2023-11-02).
## Gain more flexibility with new read/write options on your fields
With the release of new read/write options, you have more control over how fields are used by content editors and developers. As requested, you can now set the following new options on a field:
- Default - The field can be edited in the UI.
- Read only - The field can’t be edited in the UI, but it can be edited through the API.
- Hidden for non developers - The field is only visible for users with developer permissions.

## New Stack filter on content items
We are happy to bring you a new Stack field filter on content items in the GraphQL API and the Prepr UI. This feature makes it even easier to filter your content items. If you have content items with Stack fields that reference other content items, it is now possible to filter by these referenced content items. To add this filter in an API request, check out more details in [the reference docs](/graphql-api/fetching-filtering-collections#simple-reference--stack).

## Prepr's UI is now up to eight times faster
Prepr continually strives to enhance the reliability and performance of its applications. This is our primary focus, and we are always seeking ways to improve user and developer experiences.
A recent update to our database queries has significantly improved the speed of our APIs and the UI. As a result, working with Prepr has become twice as fast since this past weekend. If you use the Mutation API, GraphQL API, and the Prepr UI in a development chain, you could experience time savings of up to eight times.
If you have any feedback on the speed improvements, feel free to respond to feedback@prepr.io.
Source: https://docs.prepr.io/stay-updated/changelog2024
---
# Stay updated
We aim for full transparency and are always here to help out. Explore our newest features and [learn about improvements we're working on](/roadmap).
Source: https://docs.prepr.io/stay-updated
---
# Shared schema
*This article explains how you can create and use a shared schema in Prepr to keep the structure of your content consistent across multiple environments in an organization.*
## Use cases
The *Shared schema* feature lets organizations use the same structure for content across different brands.
While each brand might need unique content, it's smart to use the same overall structure for all brands.
In this case, create a shared schema to use the same schema across multiple environments with a shared schema.
Another use case is managing deployments across your development, testing, acceptance and production environments.
You can create entities in your non-production environment, test them, and then promote these to a shared schema when they're ready for production.
The shared schema can then be used across the whole DTAP setup.
In all use cases, the shared schema makes development faster and easier by keeping the code base leaner and more consistent.
## Create a shared schema
To create a *Shared schema*, follow the steps below.
1. Click the environment dropdown at the top right, choose your organization and click to open the environments overview.
2. Click the **Shared schema** tab to open the shared schema page.

3. Add models, components, enumerations and remote sources to complete your schema.
Check out the [Create schema docs](/content-modeling) for more details.
When completed, each of the entities in the shared schema can be used in any environment in your organization.
## Promote entities to the shared schema
You can directly promote any model, component, enumeration, or remote source from an environment to the *Shared schema* at the organization level.
Once promoted, these entities can be accessed in any environment within the same organization.
To promote a schema entity to the shared schema, follow the steps below.
1. Go to the environment for the schema entity that you want to promote and click the *Schema* page.
2. Click the model, component, enumeration or the remote source that you want to promote to open it.

3. At the top of the detailed page of the entity, click the icon and choose the **Promote to shared schema** option.
4. If the entity has no linked entities that are not yet in the shared schema, your entity will become shared.

If you don't have the *Promote to shared schema* feature enabled, [contact our Sales team](https://prepr.io/contact-sales) for more details.
## Choose field visibility for environments
You can control field visibility per environment when using a shared schema, ideal for multi-site or multi-brand setups where fields differ slightly between environments.
To choose which environments a field can be visible for editing, follow the steps below.
1. In your shared schema, go to the relevant model or component and simply click the field to open the field settings.
2. Open the **Appearance** tab and go to the *Conditional visibility* section.

3. Select the **Show field based on environment name** option and choose all the environments where this field needs to be visible.
4. Click the **Save** button to save the settings.
Now, editors will only see this field if they're working in one of the environments you've chosen in the setting above.
## Define environments for allowed models
You can control which shared content items editors can access while searching content items they want to link to their item.
Define the environments you want to allow by choosing one of the following options:
- All environments
- Current environment (the environment the content editor is working in when linking a content item)
- Or choose specific environments from the list
To choose specific environments for included models in your *Stack* or *Content reference* field, in your shared model go to the relevant field and choose the specific environment each included model is allowed.

If you switch on the **Allow new items to be added** this means that editors can create a content item directly in one of the chosen environments if they have the access.
Source: https://docs.prepr.io/project-setup/architecture-scenarios/shared-schema
---
# Shared content
The shared content feature supports multiple brands in an organization.
Sometimes, different brands want to share content within the same organization.
For example, each brand creates their own articles, but these articles belong to common categories.
This can be done with shared content.
To enable sharing of specific content, follow the steps below.
1. Go to the **Shared schema** tab in your organization.
2. Open the content item with the relevant content reference for the content you want to share.
3. Click the existing content reference field or create a new [content reference field](/content-modeling/field-types#content-reference-field) to a model.
and in the *General* tab, enable the toggle **Allow items from all environments**.

Check out the [Create schema docs](/content-modeling) for more details.
Source: https://docs.prepr.io/project-setup/architecture-scenarios/shared-content
---
# Architecture scenarios
Discover advanced Prepr CMS features tailored for more complex project structures and organizations.
Source: https://docs.prepr.io/project-setup/architecture-scenarios
---
# Blog
*This guide shows you how to model a typical blog, one of the most common patterns when implementing a web app.
Check out our [demo blog](https://acme-lease.prepr.io//blog) in action.*
## Introduction
A typical blog is made up of two key web pages, an overview page and a detailed page for each blog post.
### Overview page
In the example below, you can see the overview page includes a list of blog posts with some key content.
In this example, the list includes the post title, a cover image and an excerpt of the post.
This page also includes a list of categories that you can click to filter the posts to view similar posts.
The link in each post allows the visitor to navigate to the detailed blog post page.

### Detailed blog post
In the example below, you can see more detail about a specific post, including the main content body of the post, and author information.

You can use these sample web pages as a basis when modeling the content you need in your web app.
Start by visualizing the *Post* model first.
## Post model
When modeling your content for the blog, you can start with the main model for each post, the *Post* model.
Check out the fields you can add to the *Post* model:

|Field name |Field type | Description|
|------------------|-------------|--------|
|**Title**| [Text](/content-modeling/field-types#text-field) |The title of a post visible on the web app. Required. This title can be used to query the post through the API. |\
|**Slug** | [Slug](/content-modeling/field-types#slug-field) |Part of the URL that is used to link to the detailed post page. Required. The slug is also useful as an internal field for API queries on this post. We set it to the *Title* of the post.|
|**Cover** | [Asset](/content-modeling/field-types#asset-field) |The cover image for the post. Required. Configure presets to cater for different dimensions in the web app, for example: to display the main cover in the detailed page and when displaying the image in a card.|
|**Categories**| [Content reference](/content-modeling/field-types#content-reference-field)|A reference to the [*Category*](#category) model. The category field allows the post to be grouped which can be used in the web app in different ways, for example, to filter posts by a category.|\
|**Author**| [Content reference](/content-modeling/field-types#content-reference-field)|Used to reference the [*Author*](#author) model which has content about the people who create the posts.|
|**Content**| [Dynamic content](/content-modeling/field-types#dynamic-content-field)| This field is the main body of the post and we define it to allow the content editor to include the following elements: - Heading2, Heading3, and Heading 4 - Paragraph: Allow bold, italic, and ordered list styles as well as links within the post.- Assets: For images and videos.|
|**SEO**|[Component](/content-modeling/field-types#component-field)|An embedded SEO component. This component has fields for finding this post through search engines. |
See a complete list of all other available [Prepr field types](/content-modeling/field-types).
You'll notice that some of the fields in the *Post* model are content references (link to items from another model) and components.
You can define those referenced models and components as follows:
### Author model
You can set up an *Author* field in the *Post* model. This field is a *Content reference* to a separate *Author* model.
*Author* is set up as a model to avoid duplication of person content. Check out the fields you can define in this model.
{/* The idea is that there could be content for persons with other functions and not only for article authors. For example, imagine that employees are shown in an *About us* page. The general *Person* model caters for this use case too and makes your schema more flexible and robust. */}

|Field name |Field type | Description|
|------------------|-------------|--------|
|**Name**| [Text](/content-modeling/field-types#text-field) |The name of a person. Required.|
|**Image**|[Assets](/content-modeling/field-types#assets-field)|The asset field allows the content editor to add a photo of the person to the *Author* content item.|
### Category model
You can define a *Categories* field in the *Post* model. This field is a *Content reference* to a separate *Category* model. This makes the category content reusable in the web app. For example, different posts can have the same categories.
Check out the fields you can add to the *Category* model.

|Field name |Field type | Description|
|------------------|-------------|--------|
|**Name**| [Text](/content-modeling/field-types#text-field) |The name of a category visible on the web app. Required. This name can be used to query the category through the API. |\
|**Slug** | [Slug](/content-modeling/field-types#slug-field) |Part of the URL that is used to link to this category. Required. The slug is also useful as an internal field for API queries on this category. We set it to the `{name}` of the category.|
### SEO component
You can set up the *SEO* field in the *Post* model. This field is an embedded *Component*. SEO is set up as a component because its field structure can be reused used in multiple content items from different models, namely, the *Post*, and *Page* models.

Within the *SEO* component we define the following fields:
|Field name |Field type | Description|
|------------------|-------------|--------|
|**Meta title**|[Text](/content-modeling/field-types#text-field)|The title tag. This title is the criterion for search engines to find a web page, for example. It is also the title that appears when an item is shared on social media. |
|**Meta description**| [Text](/content-modeling/field-types#text-field) | A brief description about a particular content item, for example, this description can be included in a summary view of a post in your front-end. This is also the description used when this item is shared on social media.|
|**Meta image**| [Assets](/content-modeling/field-types#assets-field) | This image is used when displaying a link to this item, for example, the Open Graph image that you see when sharing links within social media. |
## Other use cases
In conclusion, this is just one example of how you can model a blog pattern for your web app. Feel free to reuse this structure and amend it to your needs.
You could also consider including the following use cases in your schema.
- Add the author's social media info. You could set up social media info as a component and embed it in the *Author* model.
- Create a more generic *Person* model with a field for role information. If you need to show some employee information in your front-end, it's a good idea to allow their roles to be stored for this purpose.
## Want to learn more?
Check out the following guides:
- [More example patterns](/content-modeling/examples)
- [How to create a model in Prepr](/content-modeling/managing-models)
- [How to create a component in Prepr](/content-modeling/managing-components)
Source: https://docs.prepr.io/content-modeling/examples/blog
---
# Page
*A page is one of the basic parts of a web app.
Check out our [demo home page](https://acme-lease.prepr.io/) in action.*
## Introduction
This guide takes you through the content modeling process for a page pattern and explains the reasoning behind the modeling decisions. We'll look at how to model a feature-rich web page with a variety of elements. Here is a sample web page we used as a basis for the modeling:

Let's take a closer look at the modeling steps. Using this page as a basis, we see that a number of elements make up the page content. These include:
- **A hero section**
The hero section is the prominent part of the page usually at the top. It has a heading, a sub-heading, an image, and one or more buttons.
- **A feature section**
The feature section showcases important features to highlight and consists of a heading, a sub-heading, button, image and the image position.
- **CTA**
A call-to-action to encourage interaction from the web visitor.
- **Static**
Some static sections to showcase more static info like testimonials.
- **Cards**
Cards are used for things like a selection of blog posts or recommended products. It has a heading, sub-heading, a stack of cards for the items like posts or products.
- **FAQ section**
A FAQ section to show some common questions and the corresponding answers.
- **Contact section**
This section gives visitors the opportunity to contact the company.
Let's look at how to structure a page like this in more detail.
## Page model
In our schema, we've decided to create a generic *Page* model that can be used for different kinds of web pages, for example, a home page, marketing pages, landing pages, etc.
See an example *Page* model below:

Within the *Page* model we define the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Title**| [Text](/content-modeling/field-types#text-field) |The title of the page. Required. This title is used internally to identify the page in Prepr.|
|**Slug**| [Slug](/content-modeling/field-types#slug-field) |Part of the URL that is used to link to this page. The slug can also be used for API queries to request this page and is automatically set to the title of the page in our example.|
|**Content**| [Stack](/content-modeling/field-types#stack-field)| The main content of the page. Using the Stack field allows editors to add the elements that make up the web page easily, for example, the hero section, a CTA, FAQ section, etc. |
|**SEO**| [Component](/content-modeling/field-types#component-field)| SEO is an embedded component which contains fields that are used by search engines and social media. We reuse the same SEO component defined in the [Blog pattern](/content-modeling/examples/blog) doc.|
See a complete list of all other available [Prepr field types](/content-modeling/field-types).
## How to model the elements on a page
In our example model above we put the main page content in a *Stack* field.
The *Stack* field allows content editors to easily create all the elements they need on a page.
They can add both components and content items to the stack.
The *Stack* field contains the following components and models in our *Page* model:
|Model/component |Type| Description|
|------------------|-------------|--------|
|**Hero**| Component|The hero component at the top of the page. |\
|**Feature**| Component| The feature component to allow editors to capture a heading, a sub-heading, button, image and the image position (left or right).|
|**CTA**| Component| The component for a call-to-action.|
|**Cards** | Component|The component to show recommendations or posts as cards. The editor can link existing *Product* or *Post* content items. Check out the [*Blog pattern*](/content-modeling/examples/blog#article-model) doc for more details.|
|**FAQ** | Model|A reference to existing reusable question and answer content. |
|**Contact**| Component| A component to allow editors to include a contact form in the page.|
Let's look at each element in detail.
### Hero
The *Hero* component is used to include the most prominent part of the page usually at the top.

The *Hero* component has the following typical fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Heading**| [Text](/content-modeling/field-types#text-field) |The heading usually at the top of the Hero section.|
|**Sub Heading**| [Text](/content-modeling/field-types#text-field) |More descriptive text just below the heading.|
|**Image**|[Assets](/content-modeling/field-types#assets-field)|An asset field that allows editors to include an image in the *Hero* section.|
|**Buttons**| [Stack](/content-modeling/field-types#stack-field) |One or more buttons with their respective fields.|
### Feature
The *Feature* component in a page can be used to highlight important features.

The *Feature* component typically has the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Heading**| [Text](/content-modeling/field-types#text-field) |The title of the *Image and text* block.|
|**Sub heading**| [Text](/content-modeling/field-types#text-field) |The text content.|
|**Button**|[Component](/content-modeling/field-types#component-field)|This component allows editors to label the button and to either link the button to a URL, another page in the website, or to another content item. Maximum of one button or link.|
|**Image**|[Assets](/content-modeling/field-types#assets-field)|An asset field that allows the editor to attach an image. Maximum of one asset with `image` asset type.|
|**Image position**| [List](/content-modeling/field-types#list-field)| The position of the image in relation to the text, for example, image to the left of the text or to the right of the text.|
### CTA
The *CTA* component can be used to encourage interaction from the web visitor.

The *Call to action* component typically has at least the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Heading**| [Text](/content-modeling/field-types#text-field) |The heading text for this call to action.|
|**Sub Heading**| [Text](/content-modeling/field-types#text-field) |More descriptive text for this call to action.|
### Static
The *Static* component can be used to showcase more static info such as testimonials.

The *Static* component can be made up of at least the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Title**| [Text](/content-modeling/field-types#text-field) |The title for this section.|
|**Static Type**| [List](/content-modeling/field-types#text-field) |Based on this value, the front end can determine how to display the static content, for example, testimonials or steps.|
### Cards
The *Cards* component can be used to show highlighted blog posts or recommended products.

The *Cards* component is typically made up of the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Heading**| [Text](/content-modeling/field-types#text-field) |The heading text for the collection of cards.|
|**Sub heading**| [Text](/content-modeling/field-types#text-field) |More description text just below the heading.|
|**Cards**| [Stack](/content-modeling/field-types#stack-field) |Used to include several posts or product content items.|
|**Button**| [Component](/content-modeling/field-types#component-field) |This component allows editors to label the button and to either link the button to a URL, another page in the website, or to another content item. Maximum of one button or link.|
### FAQ
The *FAQ* model is useful to include some common questions and the corresponding answers.
By creating a model instead of a component, the content is reusable and the same questions and answers can be referenced in multiple pages.

The *FAQ* model is made up of the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Internal Title**| [Text](/content-modeling/field-types#text-field) |The front end uses this field to link to the correct FAQ content item.|
|**Title**| [Text](/content-modeling/field-types#text-field) |The title for the section in the page.|
|**Questions**| [Stack](/content-modeling/field-types#text-field) |A list of questions and their corresponding answers.|
### Contact
The *Contact* component can be used to give website visitors the opportunity to contact the company.

The *Contact* component is typically made up of the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Heading**| [Text](/content-modeling/field-types#text-field) |The heading text for this contact form.|
|**Sub heading**| [Text](/content-modeling/field-types#text-field) |More descriptive text just below the heading.|
|**Form title**| [Text](/content-modeling/field-types#text-field) |The contact form title.|
|**Phone number**| [Text](/content-modeling/field-types#text-field) |Used for your company telephone number.|
|**Email**| [Text](/content-modeling/field-types#text-field) |Used to show the company email address.|
|**hubspot\_form\_id**| [Text](/content-modeling/field-types#text-field) |If you [add the HubSpot integration](/integrations/hubspot), you can use a matching `hubspot_form_id` to link to an existing HubSpot contact form.|
|**hubspot\_portal\_id**| [Text](/content-modeling/field-types#text-field) |If you [add the HubSpot integration](/integrations/hubspot), you can use a matching `hubspot_portal_id` to link to an existing HubSpot contact form.|
## What's next?
Now that you know how to model a page, let's go a step further and look at [how to set up personalization](/personalization/setting-up-personalization) doc.
## Other use cases
This guide explains how you can design just one example of a page for your web app. This page can be used for several types of pages, for example: home page or landing pages.
Feel free to use this structure and amend it to your needs.
Another use case we haven't covered could be a **Job postings** page.
In some cases, you may have to access content that is maintained in another system, for example, job postings information.
It makes sense that you may want to retrieve the items from the other system to ensure you have up to date information in Prepr.
In this example, you can include a job postings element on your page that references the job postings from the external system.
Prepr allows you to set up remote content in your model or component which is retrieved from a custom source.
Check out [how to set up a custom remote source](/content-modeling/creating-a-custom-remote-source) doc for more details.
## Want to learn more?
Check out the following guides:
- [More examples](/content-modeling/examples)
- [How to create a model in Prepr](/content-modeling/managing-models)
- [How to create a component in Prepr](/content-modeling/managing-components)
Source: https://docs.prepr.io/content-modeling/examples/page
---
# Navigation
*Navigation is a key structure that is implemented in every web app. In this article, we look at a typical navigation structure and explain the reasoning behind the modeling decisions.*
## Introduction
First, let's look at an example of a top navigation. This structure is the basis for our modeling process:

Using this example as a basis, we see that this navigation has a number of top-level (parent) menu items and these parent menu items have a number of child menu items. Prepr supports this type of nested items with the standard *content reference* field. It's possible for you to create content that references other content, even of the same *Model*. Check out the [Content reference field](/content-modeling/field-types#content-reference-field) docs for more details.
When a user clicks the lowest level child menu item, they are directed to a different page.
Let's look at the *Navigation* model in more detail.
## The navigation model
The *Navigation* model is a simple model, but is our starting point for this pattern.
See an example *Navigation* model below:

Within the *Navigation* model we define the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Title**|[Text](/content-modeling/field-types#text-field)|The title of the navigation, for example, *Top navigation*. Required. This title can be used to query the navigation through the API. |
|**Menu items**| [Content reference](/content-modeling/field-types#content-reference-field)|The navigation almost always has several menu items. This field makes a reference to the *Menu item* model. |
See a complete list of all other available [Prepr field types](/content-modeling/field-types).
Now, let's look at the *Menu items* field in more detail.
### Menu items
We set up a *Menu items* field in the *Navigation* model. This field references a *Menu item* model to avoid duplication of menu item content. The idea is that there could be content for the same menu items in different locations throughout the web app. For example, imagine that the same menu item appears in the top navigation as well as in the footer of a web page. This generic *Menu item* model caters for this situation and makes your schema more flexible and robust.

Within the *Menu item* model we define the following fields:
|Field name |Field type| Description|
|------------------|-------------|--------|
|**Title**|[Text](/content-modeling/field-types#text-field)|The title of the menu item visible on the navigation, for example, *Events*. Required. This title can also be used to query the menu item through the API. |
|**Link to page**| [Content reference](/content-modeling/field-types#content-reference-field) |When a user clicks a menu item, this field is used to open the correct internal page linked to this menu item. For more details on the Page model, check out the [Page pattern](/content-modeling/examples/page).|
|**Link to external page**| [Text](/content-modeling/field-types#text-field) |When a user clicks a menu item, this field is used to open an external page linked to this menu item. In Prepr, you can set this text field as HTML and enable the *Link* option.|
|**Description**|[Text](/content-modeling/field-types#text-field)| A short description for the menu item that is visible on the navigation, for example, *Check out our upcoming events*. |
|**Icon**|[Assets](/content-modeling/field-types#assets-field)|An optional icon that represents the menu item.|
|**Children**| [Content reference](/content-modeling/field-types#content-reference-field)|A reference to the *Menu item* model itself to create a parent-child hierarchy. The front-end queries this field to find all the children menu items for a particular parent.|
## Other use cases
In conclusion, this is just one example of how you can model any navigation for your web app. Feel free to use this structure and amend it to your needs.
- If you'd like to specify the display order of your children menu items in the content, consider adding a number field to the menu item model to specify the order. In this way, the front-end doesn't have to be updated when menu items need to be re-ordered.
## Want to learn more?
Check out the following guides:
- [More example patterns](/content-modeling/examples)
- [How to create a model in Prepr](/content-modeling/managing-models)
Source: https://docs.prepr.io/content-modeling/examples/navigation
---
# App config
*Application configuration is static information about a web app that seldom changes. In this article we look at an example of a typical application configuration model.*
{/*
You can create this pattern in Prepr automatically when you create a model. Choose the *App config pattern* template and the model described below will be created for you. */}
## Introducing the single-item-model
A lot of application configuration is not actually visible on a web app, but is essential to creating a working web app. Prepr supports this type of static once-off setup with the *single-item model*. The *single-item model* makes it much simpler for developers to query this type of content. Check out the [Single-item model](/content-modeling/managing-models#single-item-model) docs for more details.
Examples of app config content include, but is not limited to:
- *App name* - This is the name of the web app that is visible, for example when the app appears in a search list.
- *App description* - This is the brief description that is visible, for example when the app appears in a search list.
- *Company contact info* - This is information like the company's address and telephone number that is displayed on a web site.
- *Meta tags* - These are searchable tags at a web app level rather than at a page level.
- *Copyright information* - This is the static text that one often sees at the bottom of a web app to indicate copyright information.
Let's look at an *App config* model.
## An App config model
We define *App config* as a single-item model. This means that a content editor can only create one App config item. A single item also makes it easier for the front-end to query this content.
See an example *App config* model below:

Within the *App config* model we define the following fields:
|Field name |Field type | Description|
|------------------|-------------|--------|
|**App name**|[Text](/content-modeling/field-types#text-field)|The app name is listed in internet searches or in the case of a mobile app is the name search in an app store. |
|**App description**| [Text](/content-modeling/field-types#text-field) | A brief description which is shown together with the *App name* in internet searches.|
|**Company contact info**| [Text](/content-modeling/field-types#text-field) | The company's address and telephone number. This content can then be displayed in the web app.|
|**Meta tags**| [Tags](/content-modeling/field-types#tags-field) | General tags for search engines to find the web app. These tags are not specific to particular pages.|
|**Copyright info**| [Text](/content-modeling/field-types#text-field) | The static text for copyright information on the web app.|
See a complete list of all other available [Prepr field types](/content-modeling/field-types).
## Other use cases
In conclusion, this is just one example of how you can structure app config. Feel free to use this structure and amend it to your needs.
- If you would like to include a *Follow us* box in your web app, you could also design a *Social media links* field that contains links to the company's social media profiles.
- It's possible that your website uses a default CSS other than special styling for specific pages. In this case, you can also include a *Default CSS* field in your App config model.
## Want to learn more?
Check out the following guides:
- [More example patterns](/content-modeling/examples)
- [How to create a model in Prepr](/content-modeling/managing-models).
Source: https://docs.prepr.io/content-modeling/examples/app-config
---
# Content modeling examples
These examples will help you gain a good sense on how to model your schema. They explain how to set up commonly used UX patterns. Feel free to copy these examples and adjust them according to your needs.
Source: https://docs.prepr.io/content-modeling/examples
---
# Acme Lease demo website
*This guide covers info about the Prepr CMS - Next.js demo website based on a fictional car leasing company, Acme Lease.*

## Prerequisites
You need to have the following setup before you clone, run and deploy this website from the [GitHub repo](https://github.com/preprio/acme-lease).
- [A free Prepr account](https://signup.prepr.io) with an environment with [demo data](/project-setup/setting-up-environments#create-an-environment)
## About the Acme Lease Demo
This demo website is a real-world example built for Acme Lease, a fictional car leasing company.
It serves as a functional showcase of how to implement a staging website with Next.js and Prepr CMS including a personalized home page and conversion-focused landing pages.
Under the hood, the project leverages the following tech stack designed for speed and scalability:
- *Prepr CMS*
- *Next.js*
- *Apollo Client*
- [*Prepr Toolkit*](https://github.com/preprio/prepr-toolkit/tree/main)
Beyond dynamically rendered content, the demo website highlights Prepr's key marketing features.
You can explore personalized pages tailored to visitor behavior and observe active A/B tests designed to optimize conversion rates.
The site includes an accessible Prepr icon, granting instant access to preview options and experiment testing, to make sure every update is perfect before it goes live.
## Features
The Acme Lease demo website includes the following Prepr CMS features:
### Dynamic content
The [*Dynamic content* field](/content-modeling/field-types#dynamic-content-field) is a flexible editor designed for rich storytelling.
It allows editors to mix and match various elements, such as structured text, headings, tables, embedded assets and components.
It's commonly used for blog articles where you need to intersperse text with images, videos or social media embeds.
The Acme Lease demo website gives you an example on how to render various [dynamic content elements](https://github.com/preprio/acme-lease/blob/main/src/components/blog/blog-content.tsx) to display blog posts.
### Adaptive content
The core personalization engine in Prepr CMS allows marketers to create [adaptive content](/personalization/managing-adaptive-content).
Marketers can create multiple versions of a content element tailored to specific visitor segments, like *Electric Car Lovers*.
Instead of a "one-size-fits-all" approach, the CMS automatically serves the most relevant content variant in real-time based on visitor behavior, location, or data from external CRMs.
Explore how we enable the [Prepr tracking pixel](https://github.com/preprio/acme-lease/blob/main/src/app/%5Blocale%5D/layout.tsx) to show adaptive content to the right segments, like the personalized home page.
The relevant components of the page include [HTML attributes](https://github.com/preprio/acme-lease/blob/main/src/components/sections/hero-section.tsx) to trigger Prepr to calculate metrics for each personalized variant.
### A/B tests
The [A/B test](/ab-testing/running-ab-tests) feature allows marketers to test different versions of a component or text field (Variant A vs. Variant B) directly in Prepr.
By tracking impressions and conversion events (like button clicks) for each variant, marketers can identify which headlines, images, or CTAs perform better before committing to a content variant.
Explore how we enable the [Prepr tracking pixel](https://github.com/preprio/acme-lease/blob/main/src/app/%5Blocale%5D/layout.tsx) to show either the A or B variant to website visitors, like the *Electric lease* landing page.
The relevant components of the page include [HTML attributes](https://github.com/preprio/acme-lease/blob/main/src/components/sections/hero-section.tsx) to trigger Prepr to calculate metrics for each variant.
### Preview toolbar
The Preview toolbar is a powerful tool included in the [Prepr Toolkit](https://github.com/preprio/prepr-toolkit).
Content editors and marketers can use it to validate their work.
It appears as a floating Prepr icon on your staging site, allowing them to:
- Switch segments: Manually toggle between visitor segments to see how personalized content looks for each segment.
- Preview A/B Tests: Quickly swap between variant A and B in a page with an A/B test.
- Direct edit links: Click through from the preview page directly to the specific content item in your Prepr environment for instant updates.
### Coding best practices
Based on our experience and discussions with our partners, we've accumulated knowledge on some best practices when coding front-end web apps with Prepr CMS and have included the following practices in this project:
- [GraphQL *Codegen*](https://the-guild.dev/graphql/codegen) - We use this tool to automatically generate code from the the Prepr GraphQL schema and operations.
- [Section *Type* mapping](https://github.com/preprio/acme-lease/blob/main/src/types/sections.ts) - To make sure only developed sections are rendered and each section is rendered in a type safe way.
- [Query fragments](https://github.com/preprio/acme-lease/tree/main/src/queries/fragments) - To remove duplication in queries. Using fragments result in smaller queries and makes it is easier to map to component props.
## Deployment
Go to the public Acme Lease demo [GitHub repo](https://github.com/preprio/acme-lease) to clone this project and follow the installation instructions in the readme.
You can also follow the deployment instructions to deploy to Vercel in one click.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Create a Next.js complete website from scratch](/connecting-a-front-end-framework/nextjs/next-complete-guide)
- [Draft more queries for components and other field types](/graphql-api)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/acme-lease-demo
---
# Next.js Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to a Next.js project to get data from Prepr CMS. You'll learn how to make a simple blog with Next.js and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Next.js project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en)
## Create a simple Blog website with Next.js and Prepr CMS
## All done
Congratulations! You have successfully connected a Next project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Add A/B testing and Prepr personalization to your Next app](/connecting-a-front-end-framework/nextjs/next-complete-guide)
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use the Next Tailwind module](https://nextjs.org/docs/app/building-your-application/styling/tailwind-css)
- [Deploy your Next app with Vercel](https://nextjs.org/learn-pages-router/basics/deploying-nextjs-app)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-quick-start-guide
---
# Complete guide to Next.js and Prepr
*This guide shows you how to connect Prepr to a Next.js project including styling, adaptive content, A/B testing and a preview bar.*
Follow the steps below in the recommended order to set up your own working Next.js website with adaptive content, A/B testing and a preview bar.
You can also customize this project to fit the requirements for your web app.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide
---
# Caching strategies for Next.js and Apollo Client
*This article gives you insight into caching strategies and our recommendation when connecting your Next.js web app with Prepr CMS using Apollo Client.*
## Introduction
Caching is the process of storing copies of data in a temporary storage layer so that future requests for that data can be served faster.
Instead of fetching data from the CMS every time, cached data can be retrieved quickly from a nearby location like memory, a server, or a CDN.
## Caching layers
When connecting your Next.js front end to Prepr CMS using Apollo Client, caching happens at several layers.
### Next.js
There are different caching mechanisms in Next.js, each serving their own purpose:
- *Request Memorization* is only persistent per request cycle and isn’t relevant for Prepr caching strategies.
- *Data Cache* is turned off by default and can be opted in using `{ cache: 'force-cache' }` with the native `fetch` API.
- *Full Route Cache* is enabled by default, but if a route has a `fetch` request that is not cached then this will opt you out of the *Full Route Cache*. Since caching of the data cache is turned off by default, you will also not use the full route cache.
- *Router Cache* is responsible for caching layouts, loading states and pages on backwards and forward navigation.
In your front end you can configure the caching behavior for individual routes and data requests.
If you don't set any caching options when fetching from the API, both the *Data Cache* and the *Full Route Cache* are not active and data is refetched on every request.
For more details, check out the [Next.js caching docs](https://nextjs.org/docs/app/deep-dive/caching).
### Apollo Client
Apollo Client stores the results of your GraphQL queries in a local, normalized, in-memory cache.
This enables Apollo Client to respond almost immediately to queries for already-cached data, without even sending a network request.
For more details, check out the [Apollo Client caching docs](https://www.apollographql.com/docs/react/caching/overview).
### Prepr CDN
All Prepr content is served by a globally distributed content delivery network (CDN).
When you send your first API request to fetch data from Prepr, the response is cached in an edge cache location.
For more details, checkout the [GraphQL API caching doc](/graphql-api/caching).
## Recommendation
To make the most out of the dynamic Prepr features, personalization and A/B testing, we recommend setting up your front end for [SSR (server-side rendering)](/development/best-practices/csr-ssr-ssg#server-side-rendering-ssr).
Check out the [Next.js complete guide](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-2-make-the-project-dynamic) for an example Next.js project using Apollo Client.
## Prepr Next.js SSG example
If you choose not to use Prepr personalization or A/B testing, you can implement a caching strategy in Next.js and Apollo Client to build an SSG (statically generated) website.
Check out the [Prepr Next.js static GitHub repo](https://github.com/preprio/prepr-nextjs-static) for example `page.tsx` code to view the Next.js and Apollo Client cache setup.
This project also includes two webhooks, `api/post-published` and `api/post-updated` which reset the cache if triggered.
To create a simple SSG blog site, follow the steps below.
1. Clone the [Prepr Next.js static GitHub repo](https://github.com/preprio/prepr-nextjs-static).
2. Copy the `.env.example` file to a new `.env` file by running the following command:
```bash
cp .env.example .env
```
3. In the .env file, replace `{YOUR_GRAPHQL_URL}` with the *API URL* of the Prepr *GraphQL* access token from your Acme Lease demo environment.

4. Deploy the app in your preferred deployment tool. You need the deployed URL to set up the webhooks in Prepr in the next step.
5. Configure two webhooks in Prepr to listen for changed and published content and trigger your site to reset the cache.
- Set the **URL** value to `{YOUR_DEPLOYMENT_URL}/api/post-updated`, choose `content-item.changed` for **Events** and choose `Post` for **Models**.

- Set the **URL** value to `{YOUR_DEPLOYMENT_URL}/api/post-published`, choose `content-item.published` for **Events**, and choose `Post` for **Models**.

All done! Now you have a simple SSG blog post site that renders updated content whenever content is changed or published.
## Next steps
To learn more on how to expand your Next.js project, check out the following resources:
- [More data collection details](/data-collection)
- [More about A/B testing](/ab-testing)
- [More about personalization](/personalization)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/caching-strategies
---
# Next.js + Prepr CMS
Need info on Prepr for a Next.js app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up pages exactly the way your marketers want them.
## Acme Lease demo website
## Next.js Quick start guide
## Next.js Complete guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Next.js blog examples
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to set up A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs
---
# Nuxt Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to a Nuxt project to get data from Prepr CMS. You'll learn how to make a simple blog with Nuxt and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Nuxt project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en)
## Create a simple Blog website with Nuxt and Prepr CMS
## All done
Congratulations! You have successfully connected a Nuxt project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Add styling and Prepr personalization to your Nuxt app](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide)
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use the Nuxt Tailwind module](https://nuxt.com/modules/tailwindcss)
- [Deploy your Nuxt app with Vercel](https://vercel.com/docs/frameworks/nuxt)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-quick-start-guide
---
# Complete guide to Nuxt and Prepr
*This guide shows you how to connect Prepr to a Nuxt project including styling, adaptive content, and A/B testing.*
Follow the steps below in the recommended order to set up your own working Nuxt website with adaptive content, A/B testing and a preview bar.
You can also customize this project to fit the requirements for your web app.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide
---
# Nuxt + Prepr CMS
Need info on Prepr for a Nuxt app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up personalized pages exactly the way your marketers want them.
## Nuxt Quick start guide
## Nuxt Complete guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs
---
# Laravel Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to a Laravel project to get data from Prepr CMS. You'll learn how to make a simple blog with Laravel and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Laravel project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
## Create a simple Blog website with Next.js and Prepr CMS
## All done
Congratulations! You have successfully connected a Laravel project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Add styling and Prepr personalization to your Laravel app](/connecting-a-front-end-framework/laravel/laravel-complete-guide)
- [Draft more queries for components and other field types](/graphql-api)
- [Check out the Laravel GraphQL SDK repo](https://github.com/preprio/laravel-graphql-sdk)
- [Deploy your Laravel app](https://devcenter.heroku.com/articles/getting-started-with-laravel#deploying-to-heroku)
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-quick-start-guide
---
# Complete guide to Laravel and Prepr
*This guide shows you how to connect Prepr to a Laravel project including styling, adaptive content, and A/B testing.*
Follow the steps below in the recommended order to set up your own working Laravel website with adaptive content, A/B testing and a preview bar.
You can also customize this project to fit the requirements for your web app.
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide
---
# Laravel + Prepr CMS
Need info on Prepr for a Laravel app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up personalized pages exactly the way your marketers want them.
## Laravel Quick start guide
## Laravel Complete guide
## Laravel SDKs
## Other resources
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel
---
# React + Prepr CMS
This overview page gives you the resources you need to connect your React project to Prepr.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/react
---
# Vue Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to a Vue project to get data from Prepr CMS. You'll learn how to make a simple blog with Vue and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Vue project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en)
## Create a simple Blog website with Vue.js and Prepr CMS
## All done
Congratulations! You have successfully connected a Vue project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use Tailwind CSS](https://tailwindcss.com/docs/guides/vite)
Source: https://docs.prepr.io/connecting-a-front-end-framework/vuejs/vue-quick-start-guide
---
# Vue.js + Prepr CMS
Need info on Prepr for a Vue app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## Vue.js Quick start guide
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/vuejs
---
# Angular Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to an Angular project to get data from Prepr CMS. You'll learn how to make a simple blog with Angular and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Angular project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with the demo content in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en)
## Create a simple Blog website with Angular and Prepr CMS
## All done
Congratulations! You have successfully connected an Angular project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use Tailwind](https://tailwindcss.com/docs/installation/framework-guides/angular)
- [Deploy your Angular app with Vercel](https://vercel.com/guides/deploying-angular-with-vercel)
Source: https://docs.prepr.io/connecting-a-front-end-framework/angular/angular-quick-start-guide
---
# Angular + Prepr CMS
Need info on Prepr for an Angular app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## Angular Quick start guide
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/angular
---
# PHP Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to a PHP project to get data from Prepr CMS. You’ll learn how to build a simple blog with PHP and Prepr CMS. When you reach the end of this guide, you'll have a working app that looks something like the image below.*

## Prerequisites
You need to have the following setup before you connect your PHP project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
## Create a simple Blog website with PHP and Prepr CMS
## All done
Congratulations! You have successfully connected a PHP project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use Tailwind CSS](https://tailwindcss.com/docs/installation)
Source: https://docs.prepr.io/connecting-a-front-end-framework/php/php-quick-start-guide
---
# PHP + Prepr CMS
Need info on Prepr for a PHP app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## PHP Quick start guide
## PHP SDKs
## Other resources
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/php
---
# Astro Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to an Astro project to get data from Prepr CMS. You'll learn how to make a simple blog with Astro and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Astro project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en)
## Create a simple Blog website with Astro and Prepr CMS
## All done
Congratulations! You have successfully connected an Astro project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use Tailwind](https://tailwindcss.com/docs/guides/vite)
- [Deploy your Astro app with Vercel](https://docs.astro.build/en/guides/deploy/vercel/#adapter-for-ssr)
Source: https://docs.prepr.io/connecting-a-front-end-framework/astro/astro-quick-start-guide
---
# Astro + Prepr CMS
Need info on Prepr for an Astro app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr.
## Astro Quick start guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/astro
---
# Svelte Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to a Svelte project to get data from Prepr CMS. You'll learn how to make a simple blog with Svelte and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Svelte project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en)
## Create a simple Blog website with Svelte and Prepr CMS
## All done
Congratulations! You have successfully connected a Svelte project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use Tailwind CSS](https://tailwindcss.com/docs/guides/vite)
- [Deploy your Svelte app with Vercel](https://svelte.dev/docs/kit/adapter-vercel)
Source: https://docs.prepr.io/connecting-a-front-end-framework/svelte/svelte-quick-start-guide
---
# Gatsby Quick start guide
*Estimated duration: 10 minutes*
*This guide shows you how to connect Prepr to a Gatsby project to get data from Prepr CMS. You'll learn how to make a simple blog with Gatsby and Prepr CMS. By the end of this guide, you'll have a working app that looks like the image below.*

## Prerequisites
You need to have the following setup before you connect your Gatsby project to Prepr.
- [A free Prepr account](https://signup.prepr.io)
- [An environment with demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en)
## Create a simple Blog website with Gatsby and Prepr CMS
## All done
Congratulations! You have successfully connected a Gatsby project to Prepr for a simple Blog app.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Draft more queries for components and other field types](/graphql-api)
- [How to install and use Tailwind](https://tailwindcss.com/docs/guides/vite)
Source: https://docs.prepr.io/connecting-a-front-end-framework/gatsby/gatsby-quick-start-guide
---
# Svelte + Prepr CMS
Need info on Prepr for a Svelte app?
Look no further.
This all-in-one page gives you all the resources you need to connect your project to Prepr and to set up personalized pages exactly the way your marketers want them.
## Svelte Quick start guide
## Prepr Toolkit
The Prepr Toolkit enables you to set up A/B testing, personalization, a preview bar, and supports the live preview in Prepr - allowing you to seamlessly test and optimize content for different audience segments in your staging environment.
## Other resources
- [Rendering strategies, SSR or SSG?](/development/best-practices/csr-ssr-ssg)
- [How to set up personalization](/personalization/setting-up-personalization)
- [How to measure A/B testing](/ab-testing/setting-up-ab-testing)
Source: https://docs.prepr.io/connecting-a-front-end-framework/svelte
---
# Working with assets
Follow these guidelines to help you include images, videos, audios, and files in your web applications.
Source: https://docs.prepr.io/development/best-practices/assets
---
# CSR/SSR/SSG Rendering strategies
*This article explains different rendering strategies for front-end apps and makes recommendations on how to implement your strategy with Prepr.*
## Introduction
It's important to decide on a rendering strategy for your front-end apps in the early stages of your implementation. The strategy you choose should match the type of content that your front end renders. For example, do you need to render a lot of static content such as documentation or urgent dynamic content like on an ecommerce site. We look at different types of rendering and any special considerations when implementing your front end alongside Prepr.
## Rendering types
|Rendering type| Applicable Technologies |
|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|[Client-side rendering (CSR)](#client-side-rendering-csr) The original way to render web pages with dynamic content when JavaScript was first introduced. When a user visits a page, they see it only after the script has finished.| [React](/connecting-a-front-end-framework/react), [Vue.js](/connecting-a-front-end-framework/vuejs), [Angular](/connecting-a-front-end-framework/angular), [Next.js](/connecting-a-front-end-framework/nextjs), [Nuxt](/connecting-a-front-end-framework/nuxtjs). |
|[Server-side rendering (SSR)](#server-side-rendering-ssr) The server generates the HTML on a request for a page. This means that when a user navigates to a page, they will see a fully rendered UI immediately.| [React](/connecting-a-front-end-framework/react), [Vue.js](/connecting-a-front-end-framework/vuejs), [Angular](/connecting-a-front-end-framework/angular), [Next.js](/connecting-a-front-end-framework/nextjs), [Nuxt](/connecting-a-front-end-framework/nuxtjs), [Node.js](/connecting-a-front-end-framework/nodejs). |
|[Static site generation (SSG)](#static-site-generation-ssg) A bunch of static files for the entire site are generated during deployment. This means that a visitor can see and interact with the pages immediately.| [React](/connecting-a-front-end-framework/react), [Vue.js](/connecting-a-front-end-framework/vuejs), [Angular](/connecting-a-front-end-framework/angular), [Next.js](/connecting-a-front-end-framework/nextjs), [Nuxt](/connecting-a-front-end-framework/nuxtjs), [Node.js](/connecting-a-front-end-framework/nodejs). |
|[Hybrid rendering solutions](#hybrid-rendering-solutions) Some frameworks offer a hybrid rendering solution that uses the best of both SSR and SSG. | [See below](#hybrid-rendering-solutions) for more details on the hybrid solutions offered by [Next.js](/connecting-a-front-end-framework/nextjs), [Nuxt](/connecting-a-front-end-framework/nuxtjs). |
## How to choose a rendering type
It is important to think about how frequently your content changes and how urgent these content changes are. For example, a blog site does not usually require multiple updates in a day, so static generation is a good option. On the other hand, product information on an ecommerce site needs frequent updates, so rendering needs to happen more dynamically.
Below are other metrics to consider when you choose a rendering strategy.
|Metric|CSR|SSR|SSG|Hybrid|
|----------|-------|-------|-------|-------|
|**Data integrity** How up to date the data is when a page loads.| | | | |
|**SEO** How easy it is for search engines to find the page content.| | | | |
|**Performance** How quickly a page loads. | | | | |
|**Build Time** How quick it is to build an app. | | | | |
Watch the video below for the key differences betweens SSG and SSR.
## Client-side rendering (CSR)
Client-side rendering (CSR) is rendering pages directly in the browser for every page request using JavaScript. This type of rendering is mainly used for single-page applications (SPA) because the single page needs to be refreshed each time it's loaded. This means the data is always up to date, but large payloads will slow down the loading of a page.

## Server-side rendering (SSR)
Server Side Rendering (SSR), also known as dynamic rendering, is when the HTML of a site is generated on the server, then sent to the browser. This type of rendering is faster than CSR for large payloads and is useful for SEO purposes, because search engines can access the content. See a simple SSR process flow below.

## Static site generation (SSG)
SSG is a type of pre-rendering where the process of compiling and rendering a web app happens during the build time. The output of the build is a bunch of static files, including the HTML file as well as assets like JavaScript and CSS. Updates to pages are not rendered until a new build. Because all the pages are pre-rendered, the page load is faster than SSR, but any changes to content will not be re-rendered on the fly.

## Hybrid rendering solutions
A hybrid rendering solution gives developers the option to get benefits from both SSR and SSG. The following solutions are available in a few frameworks:
- **Page-by-page**: This means that each page of your project can be rendered either statically or dynamically. This method is available in Next.js. Check out the [Next.js](https://nextjs.org/docs/pages/building-your-application/rendering) docs for more details.
- **Route-based rendering**: This solution allows you to render specific routes (URL paths) statically or dynamically. The route can be at subdomain level which means that groups of pages can be rendered statically or dynamically. Check out the [Next.js docs](https://nextjs.org/learn-pages-router/basics/dynamic-routes) and the [Nuxt docs](https://nuxtjs.org/docs/features/rendering-modes) for more details.
- **Incremental static regeneration (ISR)** and **Deferred static generation (DSG)**: These hybrid solutions aim to use the best of both SSR and SSG. When a user visits a page, the front end triggers the regeneration (also known as revalidation) of the page, but the user sees the *static page* immediately. If the user or another user visits the same page a few seconds later, the newer page is rendered. Unlike SSG, you can choose the number of pages to build to reduce the build time and performance is better than SSR because the request does not wait for the rendering of a page to complete. ISR was developed by Next.js on Vercel while DSG is the Gatsby solution. Another version of this solution is possible in Nuxt deployed on Layer0. See a simple process flow below: 
## Schedule a free consultation
Do you still have questions on rendering strategies in relation to Prepr? Book a 15-minute call with a Prepr solution engineer right away. [Schedule a call](https://prepr.io/get-a-demo)
Source: https://docs.prepr.io/development/best-practices/csr-ssr-ssg
---
# Handling redirects
*This article explains how to handle redirects for your content items in Prepr CMS*
## Introduction
Redirects send visitors from a requested URL to another. You often set up redirects in your framework-specific front-end app or in a redirects file for a hosting provider for pages that are broken or pages that were moved to new URLs to ensure visitors and search engines access the most relevant or current page.
## Handle redirects with a headless CMS
It's only natural that content items can change. When the changes include the URL that links to the content item, this results in a broken link. To prevent this situation, a developer sets up a redirect to display a working page when an outdated link is visited.
In the case where you have content items that could potentially have changes to the URL that links to it, for example, when updating an existing article with fresher content including the name of the link to the article, follow the steps below to reduce the number of broken links in your front end.
## Step 1: Model the redirect in Prepr CMS
The first step is to model the redirect to make it possible to manage redirect entries directly in Prepr CMS.
The *Redirect* model includes the following fields:
- The outdated URL, often known as the *Source*.
- The new target URL, known as the *Destination*.
- An indicator to choose the type of redirect, for example, `permanent` or `temporary`.
See the examples below for typical redirect config in a front-end framework like Next.js and for server redirect config like a Vercel deployment.
A slug is the end part of a URL after the last backslash and identifies a unique page in a front end.
In Prepr CMS, you can create a [slug field](/content-modeling/field-types#slug-field) in a model to maintain a unique value for each content item.
This slug value is then used in your front-end app to set up page routing to that content item. Check out the [Next Quick start guide](/connecting-a-front-end-framework/nextjs/next-quick-start-guide#step-4-fetch-individual-articles) for an example implementation.
With this in mind, follow the steps below to create a *Redirect* model.
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add model** button.
3. Click **Multi-item model** and **Next** again.
4. Enter *Redirect* as the *Name* and click **Save**.
5. Add a *Text* field type to save the *Internal title* to easily identify a redirect record.
6. Drag and drop the *Slug* field type into your model.
Switch to the **Validation** tab and enable the **This field is required** toggle.
7. Drag and drop the *Content reference* field type, from the list on the right into your model.
a. Enter *Destination* as the *Name*.
b. Select the models you want to allow to have redirects.
c. Switch to the **Validation** tab and enable the **This field is required** toggle.
d. Click the **Save** button.
8. Drag and drop the *Boolean* field type into your model and enter *Permanent* as the *Name*, set a *Default value* of **True** and click the **Save** button.

Now that the *Redirect* model is done, move on to the next step to add redirect entries in Prepr CMS.
## Step 2: Create a redirect in Prepr CMS
Follow the instructions below to add a redirect entry manually.
1. Go to the **Content** tab.
2. Click **Add item** and select the **Redirect** model created in the previous step.
3. Enter the *Slug* of the outdated URL in the format expected by your framework or host redirects file.
4. For the *Destination* field, choose the relevant content item for which the slug was changed.
5. Enable the permanent field, if applicable, and click the **Publish** button.

That's it! Once the redirect entries are added to Prepr, you can then fetch the entries and process them.
## Step 3: Handle redirects
This guide outlines two methods to handle redirects. One is based on handling redirects in a Next.js project by dynamically adding the redirects to the `next.config.js` file. The other is handling redirects in an Astro project by adding the redirects to the `vercel.json` which gets picked up and processed when the project is deployed in Vercel.
Follow the steps below to create a script to fetch the redirects info from Prepr CMS and generate the redirects to either be included in the `next.config.js` or written to the `vercel.json`.
1. Create the script `getRedirects` to fetch the redirects like in the code snippets below. Set the placeholder value for `` to the API URL from a [Prepr access token](/graphql-api/authorization#access-tokens).
2) Use one of two methods to update the configuration file to handle the redirects.
- **Redirect with Next.js**: Add the redirects from Prepr to the built-in `next.config.js` file.
- **Redirect with Vercel**: Add the redirects from Prepr to the `vercel.json` file during build time so that Vercel processes them after deployment.
That's it! Once you successfully build and deploy your project, the redirects are processed and can be tested by navigating to the outdated links.
## What's next?
Check out our other best practice guides from the list below:
- [Rendering strategies](/development/best-practices/csr-ssr-ssg)
- [Using TypeScript with the GraphQL API](/development/best-practices/typescript)
- [How to set up content item previews](/project-setup/setting-up-previews-and-visual-editing)
Source: https://docs.prepr.io/development/best-practices/redirects
---
# Handling SEO in a headless CMS
*This article details how to solve SEO challenges when implementing a headless CMS.*
## Introduction
SEO is the process of making a website easy to find through search engines like Google.
So, setting up SEO measures is a necessary activity when implementing a front end.
And this process has been made easier with the introduction of the CMS.
Headless CMSs are becoming the more popular choice over traditional CMSs because of consistency across multiple platforms, flexibility, improved performance, and scalability.
Despite these benefits, the very nature of a headless CMS that decouples the front end from the content means that there are some challenges when taking measures to improve SEO.
## SEO challenges with a headless CMS
Traditional CMS platforms often come with built-in SEO tools that headless CMSs don't usually have. This means that developers need to implement an SEO strategy in the front-end application to deal with challenges when implementing a headless CMS.
When you prepare an SEO strategy, consider the following topics:
- Managing metadata
- Setting up URLs
- Managing redirects
- Generating a sitemap
- Reviewing content for SEO
Let's look at each of these SEO topics in detail and how to handle them in Prepr CMS.
### Managing Metadata
Metadata are elements like title and meta description with keywords that provide information about a webpage to search engines and web browsers. These elements are placed in the head HTML of a webpage and are used to help search engines understand the content and context of the page.
Unlike headless CMSs, traditional CMSs usually include plugins that can customize and update meta tags for pages. So, some once-off development effort is needed to set these up dynamically in a headless CMS.
#### How to manage metadata in a headless CMS
Headless CMSs decouple content from the front-end presentation. Before implementing a headless CMS project, you will typically model the content first. During this stage you will model the SEO metadata.

Important metadata include fields like the `Meta title` and `Meta description` like in the example above. For the purposes of sharing content on social media, you could also include a `Meta image`. You could even include a `priority` to indicate the pages that are most important for crawling and indexing. Use indicators like `nofollow` and `noindex` for content that should be ignored by searchbots.
Use the guidelines below to set up the SEO metadata structure:
- Keep the title tag short. Restrict the number of characters to 60.
- Keep the metadata description to under 140 characters.
- Use [robots metadata](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag-implementation) to tell search engines which pages to avoid and not index. By default, Googlebot will index a page and follow links to it.
- Prevent content editors from using the same metadata description for multiple pages in your website
- Include some help text on the SEO fields to guide content editors with the following tips:
- Use a persuasive metadata description to get searchers to click your page.
- Include keywords in your metadata description.
- Set the metadata title tag to the H1 (title) of the page where it makes sense to do so.
- Show intent in the metadata title tag.
- Marketers should revise the title and meta description on pages based on the CTR for those pages.
Once your structure is defined in the CMS, it's available to content editors and marketers to fill in values for fields like the title and the description. In the front end, it's then possible to retrieve the metadata to set the necessary tags for each page.
#### How to set up metadata in Prepr CMS
You can easily manage SEO metadata in Prepr CMS in a few steps.
1. Set up a template structure for the SEO metadata by creating an SEO component.

2. Include the SEO component in relevant models. These are the models that need to be rendered as pages in the front end.
3. The content editor can then store SEO metadata like the title, meta description, and set `Noindex` and `Nofollow` tags.
4. In the front end, include code that makes a [GraphQL API](/graphql-api) request to get the SEO metadata for the relevant page and map the values to their relevant HTML meta tags. Check out the [Google meta tags and attributes doc](https://developers.google.com/search/docs/crawling-indexing/special-tags) for attributes that are relevant to search engines.
It's useful to know that many front-end frameworks abstract the creation of the meta tags and allow you to set meta tags dynamically. See the example code snippets below on how to set meta tags.
### Setting up URLs
Search engines like Google use URLs as one of the factors for website ranking and to understand what the content of a page is about. SEO-friendly URLs improve indexing by search engines.
Also, when visitors see a URL, they are more likely to click a URL they understand compared to a URL that has a bunch of numbers.
Unlike headless CMSs, traditional CMSs include plugins that set URLs automatically. For instance, they offer built-in support for creating language-specific URLs, which is crucial for SEO. This can be easier to manage because the system automatically handles URL structures based on language settings. On the other hand, headless CMSs need custom development to manage URLs. You need to explicitly configure and maintain URL structures which is a more flexible solution but needs more technical input.
#### How to set up URLs in a headless CMS
Many headless CMSs allow editors to include a URL or slug for the page. The slug is the last part of the URL address that uniquely identifies a page. If possible, auto-generate this slug field value based on existing fields in the page content such as the page `name` or an article `headline`. The front end can then get this slug value to set up the URL for a page.

To improve SEO, make sure that the generation of the slug follows the guidelines below.
- Use short descriptive URLs that are easy to read like `example.com/best-practices/how-to-handle-SEO`.
- Avoid complicated URLs with unrecognizable words or numbers like `example.com/products/page-id-1231/2019-09`.
- Keep URLs constant. Search engines consider a new URL to be a link to a new page. If a URL changes for an existing page, the ranking of the page starts from scratch and it will take time for the page to regain its ranking in searches.
- Use hyphens '-' in URLs and not underscores '\_' as separators.
- Only use lowercase in URLs.
- Use primary keywords in the URL, but don't repeat the keyword.
Follow the guidelines below for SEO when setting up URLs in the front-end.
- Organize your URLs into categories and sub-categories like `blog` or `best–practices` to improve content ranking.
- Handle not-found pages and use [redirects](#managing-redirects).
In the case of language-specific URLs, it's useful to note that many front-end frameworks include dynamic routing for internationalization. Check out the [Next.js docs](https://nextjs.org/docs/pages/building-your-application/routing/internationalization) for an example.
### Managing Redirects
Redirects send visitors from a requested URL to another. You need redirects for pages that are broken or pages that were moved to ensure visitors and search engines access the most relevant or current page.
Without the usual front-end management that you find in a traditional CMS, handling redirects can be more complex for a headless CMS.
#### How to handle redirects in a headless CMS
You can set up a simple content structure in the CMS to hold the original URL and the new URL for a page that needs to be redirected. In this structure, include an indicator field to define the redirect as `permanent` or `temporary`.

Once the redirect structure is modeled, add a list of redirects to the CMS content. The front end can then query and send the redirect info to the deployment service.
Check out the [Prepr Redirects guide](/development/best-practices/redirects) for example code snippets on how to handle redirects using Prepr CMS.
### Generating a sitemap
A sitemap is a file that helps visitors and web crawlers navigate your website to discover and index pages. With a sitemap, you can ensure that search engines can discover your pages quickly and when a crawler finds a URL in your sitemap, it knows you want it in the search results.
Traditional CMSs often include built-in support to generate a sitemap automatically. While headless CMSs don't include automatic sitemap generation for search engine crawling. Additional development effort is therefore needed for a headless setup.
#### How to generate a sitemap in a headless CMS
The sitemap structure should follow the guidelines below:
- The XML sitemap format provides additional information about images, video, and localized versions of your pages.
- The sitemap should include fully-qualified, absolute URLs.
- The `` tag must be consistently updated to indicate when the content last changed.
A sitemap for a project contains a list of all the pages that it wants the search engine to find like in the example below.
```xml filename="sitemap.xml" copy
https://www.xmlsitemapgenerator.org/en/
2005-01-01
monthly
0.8
```
Headless CMSs allow you to include URL or slug info for each content page. The front end application can then retrieve this info for each page and compile the sitemap structure dynamically during deployment.
#### Generate a sitemap dynamically from Prepr CMS
You can make use of the available page content in Prepr CMS to generate the sitemap with the steps below.
1. Make the `base URL` available in an environment variable.
2. Query the slugs for each of your page content items.
3. Loop through these items and assign them to an array.
4. For each page in the array, set the URL in the sitemap to the `base URL` + `slug` and set the `lastmod` value to the system date field `_changed_on` of the content item.
See the simple Next.js code snippet example below that automatically generates a sitemap from Prepr CMS during the build of the web app.
```js filename="sitemap.js" copy
let pages = []
const { data } = await client.query({
query: gql`
query {
Pages {
items {
_slug
_changed_on
}
}
}
`,
})
data.Pages.items.map((page) => {
pages.push({
url: process.env.SITE_URL + '/' + page._slug,
lastModified: new Date(page._changed_on),
changeFrequency: 'monthly',
priority: 1.0,
})
})
return pages
}
```
### Reviewing Content for SEO
Search engines prioritize fresh and relevant content, so outdated content loses visibility in search engine results. This leads to a decline in organic traffic and a diminished online presence. When using a headless CMS, you can easily publish content updates to the front end whenever the changes are needed.
However, a headless CMS usually lacks a WYSIWYG editor and SEO plugins to measure content performance. This makes it difficult to preview how content changes will look and perform before the front end goes live.
#### How to review content in headless CMSs
Most CMSs do not include tools to review the content for SEO impact. In these cases, you need separate monitoring software to review the content for SEO performance in search engines.
#### How to review content in Prepr CMS
Prepr CMS includes an [automatic SEO review feature](/content-management/managing-content/optimizing-content-for-seo) and the ability to include a [front end preview](/project-setup/setting-up-previews-and-visual-editing) for each piece of content.
For more advanced SEO performance criteria, consider a dedicated marketing monitoring tool or SEO website crawler software.
## Schedule a free consultation
Do you still have questions on SEO in relation to Prepr? Book a 15-minute call with a Prepr solution engineer right away. [Schedule a call](https://prepr.io/get-a-demo)
Source: https://docs.prepr.io/development/best-practices/seo
---
# Using TypeScript with the GraphQL API
*This article explains how to make the most out of Prepr GraphQL API features when using TypeScript in your front-end app.*
## Introduction
TypeScript is a strongly typed programming language that builds on JavaScript.
Developers choose to use TypeScript instead of JavaScript in their projects to take advantage of cleaner and more scalable code, better code readability, type safety, and code that's easier to debug and test.
By using TypeScript in *Strict Mode*, you get stronger guarantees of program correctness for types and null checks.
You can use a GraphQL code generator to generate TypeScript types and keep these up to date with any schema changes.
Prepr delivers strict types in the GraphQL API schema so that TypeScript in [strict mode](https://www.typescriptlang.org/tsconfig#strict) can support the following situations in the front end:
- To process content fields in their expected data types.
- To make sure that some fields in content items are not null before rendering the content in the front end.
## Enable strict mode and generate TypeScript types
Follow the steps below to use the strict mode feature in Prepr CMS, generate TypeScript in your front end and process an API response with strong TypeScript types.
Let's start with the setup in your Prepr environment.
And that's it! You've successfully implemented TypeScript strict mode in your front-end.
If you found this article useful and would like to explore other similar topics in the Prepr docs, [please let us know](mailto:feedback@prepr.io).
Source: https://docs.prepr.io/development/best-practices/typescript
---
# Integrating with webhooks
*This guide shows you how to configure, implement and secure webhooks to enable real-time data sync from Prepr CMS to external applications.*
## Introduction
Webhooks in Prepr CMS let you receive real-time updates for any events.
Whenever a content item, asset, segment, visitor, schema or tag changes, Prepr sends a secure HTTP `POST` request to your webhook endpoint.
This allows your application to respond to an event triggered in Prepr — whether that's syncing data, triggering notification or other automation, or keeping your UI in sync with content changes.
## Creating a webhook
Webhooks in Prepr send notifications as `POST` requests and include a `JSON` payload with Prepr headers and custom headers, if defined in the webhook.
Follow the steps below to add a new webhook.
1. Log in to your Prepr account with a user that has *Owner*, *Admin* or *Developer* rights.
2. Click the icon and choose the **Webhooks** option.
3. Create a new webhook by clicking the **Add webhook** button.
4. Add the URL of the endpoint where the notifications will be sent.

5. Select one or more of the [supported events](#supported-events) to trigger the request.

6. Click the **Save and close** button.
Check out the list below for additional info you can include in your webhook.
### Filtering by model
When you select a content item event, you will receive notifications when any content item is created, published, unpublished, changed, or deleted.
You can further specify which content items you want to receive notifications from.
Select one or more models to receive webhook events for content items of that specific model.

### Custom headers
There are some use cases where an application requires some fields to be added in the header of the webhook payload.
For example, for access tokens. To add a custom header, enter a header key and value.

## Supported events
The events below can be used to trigger a webhook notification.
Depending on your plan some options may not be available.
### Content item events
| Event Name | Description | Example follow-up action |
| :--- | :--- | :--- |
| `content_item.published` | When a user publishes a content item. | Trigger a static website to rebuild. |
| `content_item.unpublished` | When a content item is moved to *Unpublished* status. | Remove an article from a *Featured* list. |
| `content_item.created` | When a new content item is created. | Send a notification to the content production manager. |
| `content_item.changed` | When a content item is changed. | Notify a developer when web app config is changed and might have impact on the front end. |
| `content_item.deleted` | When a content item is moved to the *Deleted items* list. | Notify a developer when system-related content items are deleted and will impact the front-end. |
| `content_item.invalidated` | 1. When the remote source item used in a content item has changed in the external system. 2. When an [API `PATCH`](/mutation-api/content-items-create-update-and-destroy#patch-a-content-item) is requested for a published content item. | Trigger the front end to clear the UI cache to track *Out of stock* products. |
### Asset events
| Event Name | Description | Example follow-up action |
| :--- | :--- | :--- |
| `asset.created` | When an image, file or video is initially created in the *Media library*. (Not yet uploaded) | Send the asset ID to automatically link to a migrated content item. |
| `asset.uploaded` | When an image, file or video is uploaded to the *Media library*. | Trigger an AI service to generate alt-text. |
| `asset.changed` | When the meta-data of the image, file or video is updated like the title, description or the video thumbnail is changed. | Trigger an AI service to regenerate alt-text. |
| `asset.deleted` | The image, file or video is removed from the *Media library*. | Send a notification to the content production manager. |
| `storage_file.deleted` | The uploaded file is deleted from storage. | Trigger removal from your own storage. |
| `cdn_file.deleted` | File is purged from the CDN. | Trigger removal from your own CDN. |
### Schema events
| Event Name | Definition | Example follow-up action |
| :--- | :--- | :--- |
| `schema.changed` | The data model or field types changed. | Trigger TypeScript regeneration in the front end. |
### Segment events
| Event Name | Definition | Example follow-up action |
| :--- | :--- | :--- |
| `segment.created` | When a segment is created. | Create a new corresponding target audience in your CRM. |
| `segment.changed` | When a segment is changed, such as the name, description or conditions. | Sync the updated info to your CRM. |
| `segment.deleted` | When a segment is removed. | Stop all automated workflows associated with this segment. |
### Visitor events
| Event Name | Definition | Example follow-up action |
| :--- | :--- | :--- |
| `person.created` | When a new profile is added to the list of web app visitors. | Trigger a *Welcome* email to the new visitor. |
| `person.changed` | When the visitor data like the email or name is modified. | Sync the updated info to your CRM. |
| `person.deleted` | When a profile is deleted. | Invoke "Right to be Forgotten" protocols for GDPR compliance. |
| `person.segment.added` | When the profile is added to a segment. | Trigger a campaign email the qualifying visitor. |
| `person.segment.removed` | When the profile is removed from a segment. | Revoke a discount code or move them to a different list. |
### Tag events
| Event Name | Definition | Example follow-up action |
| :--- | :--- | :--- |
| `tag.created` | When a new tag is added. | Add the tag to the global filter list in the front end. |
| `tag.changed` | A tag name or property is updated. | Bulk-update the display label on all tagged items. |
| `tag.deleted` | A tag is removed. | Remove the tag from the global filter list in the front end. |
## Payload examples
The table below outlines the structure of the webhook request, detailing key parameters and their associated data.
Each parameter represents an aspect of the event, including metadata.
| Field name | Field type | Description |
|-------------------------|------------|----------------------------------------------------------|
| `id` | string | The event unique identifier. |
| `created_on` | string | The timestamp in UTC when the event is created. |
| `event` | string | The triggered event, for example `content_item.published`.|
| `payload` | object | Object with info about the resource that triggered the event.|
| `performed_by` | string | The Prepr user who triggered the event.|
| `first_publish` | boolean | A value of `true` or `false` is filled when the event is `content_item.published`. |
| `locale` | string | The language such as `en-US` is filled when the event is `content_item.changed`, `content_item.deleted`, `content_item.published`, `content_item.unpublished` or `content_item.invalidated`.|
| `scope` | string | Filled when the event is `content_item.deleted`. The value is either `item` for a full content item deletion or `locale` for a partial delete.|
The sections below describe the payload for each resource in more detail.
### Content item example
The below example request is the payload object when a `content_item.published` event triggers for a `Post` model.
```json copy
{
"id": "75ec2d8c-e5ba-452e-be08-3524af2e1f49",
"created_on": "2025-03-19T14:30:49+00:00",
"changed_on": "2025-05-28T09:01:45+00:00",
"label": "Publication",
"read_time": {
"en-US": 2,
"nl-NL": 2
},
"publish_on": {
"en-US": "2025-05-28T09:01:00+00:00",
"nl-NL": "2025-02-19T12:33:00+00:00"
},
"slug": {
"nl-NL": "de-top-5-mythes-over-autoleasing-ontkracht",
"en-US": "the-top-5-myths-about-car-leasing-debunked"
},
"model": {
"id": "4a8b8f49-b5cd-412f-97e0-5b04a072688d",
"body_singular": "Post",
"body_plural": "Posts"
}
}
```
| Field name | Field type | Description |
|-------------------------|------------|----------------------------------------------------------|
| `id` | string | The content item unique identifier. |
| `created_on` | string | The timestamp in UTC when the item is created. |
| `changed_on` | string | The timestamp in UTC when the item is changed. |
| `label` | string | Type of resource. For content items, this value is `Publication`.|
| `read-time.{locale}` |integer| Values are listed for each locale, for example `en-US`. The calculated time a user reads an article in minutes.|
| `publish_on.{locale}` |string |Values are listed for each locale, for example `en-US`. The timestamp in UTC when the item is published. |
| `slug.{locale}` |string|Values are listed for each locale, for example `en-US`. The slug value of the content item, if applicable.|
| `model.id` |string|The internal id of the model.|
| `model.body_singular` |string|The singular name of the model, for example `Post`.|
| `model.body_plural` |string|The plural name of the model, for example `Posts`.|
### Asset example
The below example request is the payload for an `asset.changed` event.
```json copy
{
"id": "c8580913-bbac-4309-9f74-95a6835930b8",
"created_on": "2025-03-19T16:01:14+00:00",
"changed_on": "2025-05-27T15:23:33+00:00",
"label": "Photo",
"name": "bmw",
"body": "",
"author": "",
"status": null,
"replaceable": true,
"reference_id": "b773b7b3-97ce-40c3-ae1f-b1369d0516e1",
"reference": null,
"original_name": "img",
"mime_type": "image/png",
"extension": "png",
"height": 250,
"width": 496
}
```
| Field name | Field type | Description |
|-------------------------|------------|----------------------------------------------------------|
| `id` | string | The asset unique identifier. |
| `created_on` | string | The timestamp in UTC when the asset is created. |
| `changed_on` | string | The timestamp in UTC when the asset is changed. |
| `label` | string | Type of resource. For assets, this value is `Photo`, `Video` or `Document`.|
| `name` | string | The name of the asset. |
| `body` | string | Additional information about the asset. |
| `author` | string | The person who created the asset such as the photographer.|
| `reference_id` | string | The external id of the asset. |
### Segments example
The below example request is the payload for a `segments.deleted` event.
```json copy
{
"id": "sgm_5g4820qaz70",
"created_on": "2025-05-28T08:52:19+00:00",
"changed_on": "2025-05-28T08:52:19+00:00",
"synced_on": "2025-05-28T08:52:19+00:00",
"label": "Segment",
"body": "New segment",
"description": null,
"reference_id": "new-segment",
"query": {
"conditions": [
{
"type": "event",
"event_name": "View",
"result": true
}
]
},
"mode": 2,
"count": null
}
```
| Field name | Field type | Description |
|-----------------------|------------|----------------------------------------------------------|
| `id` | string | The segment unique identifier. |
| `created_on` | string | The timestamp in UTC when the segment is created. |
| `changed_on` | string | The timestamp in UTC when the segment is changed. |
| `label` | string | Type of resource. For segments, this value is `Segment`.|
|`body` | string | The name of the segment as it's displayed in the CMS.|
|`reference_id` |string | An external id for the segment. This value can be used to identify the segment in an external CRM system, if applicable.|
|`query.conditions` |array | A list of condition objects for this segment. The object fields depend on the condition options saved for this segment. For a complete list, check out the [segments doc](/personalization/managing-segments).|
### Visitor example
The below example request is the payload for a `person.changed` event.
```json copy
{
"id": "3b0e2f5e-5b06-4563-828d-13ce49252672",
"created_on": "2024-08-23T07:20:45+00:00",
"changed_on": "2025-05-27T15:48:20+00:00",
"last_seen": "2024-08-23T07:20:45+00:00",
"label": "Person",
"reference_id": null,
"first_name": "Braylen",
"last_name": "Nash",
"full_name": "Braylen Nash"
}
```
| Field name | Field type | Description |
|--------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | string | The visitor unique identifier. |
| `created_on` | string | The timestamp in UTC when the visitor is created. |
| `changed_on` | string | The timestamp in UTC when the visitor is changed. |
| `label` | string | Type of resource. For visitors, this value is `Person`. |
| `full-name` | string | The full name of the visitor. For a full list of available visitor characteristics, checkout the [managing visitors guide](/data-collection/managing-visitor-data-manually#updating-visitor-profiles). |
### Tag example
The below example request is the payload for a `tag.created` event.
```json copy
{
"id": "c921ef1e-b8e5-4e8a-8cfa-2914665c3bf1",
"created_on": "2025-09-03T12:40:49+00:00",
"changed_on": "2025-09-03T12:40:49+00:00",
"label": "Tag",
"color": null,
"body": "subscriber",
"slug": "subscriber"
}
```
| Field name | Field type | Description |
|--------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | string | The visitor unique identifier. |
| `created_on` | string | The timestamp in UTC when the tag is created. |
| `changed_on` | string | The timestamp in UTC when the tag is changed. |
| `label` | string | Type of resource. For tags, this value is `Tag`. |
| `body` | string | The value of the tag. |
## Response
View the full list of notifications sent to your webhook on the *Webhook detail* page in your Prepr account.
From here you can view the response the Prepr server received from your endpoint.

Your endpoint must respond with a `2xx` status code within 12 seconds. Any other status code or a
request timeout is treated as a failure and triggered for a retry.
Check out the options below on how to handle responses with errors.
### Retry behavior
Prepr attempts multiple times to deliver a given notification to your webhook endpoint with an exponential back off.
You can configure a maximum number of retries.
If your endpoint has been disabled you can still expect to see future retry attempts.
### Disable behaviour
Prepr will actively notify the account owner and any [*Technical contact*](/project-setup/managing-roles-and-permissions#technical-contact) users when the webhook is misconfigured or the error rate is 100%.
The email also states when the endpoint will be automatically disabled.
### View attempts
To manage an existing webhook, go to **Settings → Webhooks** and click the specific webhook in the list.
All attempts of this webhook of the last three days can be viewed and retried here.
You can also view the response the Prepr server received from your endpoint.
### Resend webhooks
You can resend a webhook event by clicking the icon.
### Disable webhooks
You can easily switch off your webhook temporarily.
Go to **Settings → Webhooks** and click the specific webhook in the list.
Switch it off by disabling the **Active** toggle.
## Security guidelines
We recommend you protect the endpoints configured in each webhook. This prevents unauthorized actions on your app.
### Authorization headers
When configuring your webhook in Prepr, add an authorization *Header* with a secret access token only your incoming webhook server knows.
Prepr will send all of the custom headers you added when sending a notification, including authorization.
If the values don't match, you can ignore the request.
### Whitelisting Prepr server IPs
You can ensure your app is always communicating with Prepr through one of our IP addresses.
```http copy
87.233.165.0/26
2001:9a8:0:4b:: 64 bits
```
### Webhook signatures
Prepr will sign the webhook events it sends to your endpoints. We do so by
including a signature in each event’s `Prepr-Signature` header. This allows
you to validate that the events were sent by Prepr, not by a third party.
Before you can verify signatures, you need to store your endpoint’s ID
when creating a new Webhook. Each ID is unique to the endpoint to which
it corresponds. If you use multiple endpoints, you must obtain an ID for each one.
**Verifying signatures**\
The `Prepr-Signature` header contains the full json payload you receive.
Prepr generates signatures using a hash-based message authentication code (HMAC) with SHA-256.
Compute an `HMAC` with the `SHA256` hash function. Use the endpoint’s signing ID
as the key, and use the json payload string as the message.
Compare the signature in the header to the expected signature. If a signature matches,
compute the difference between the current timestamp and the received timestamp,
then decide if the difference is within your tolerance.
To protect against timing attacks, use a constant-time string comparison to
compare the expected signature to each of the received signatures.
### Preventing replay attacks
A replay attack is when an attacker intercepts a valid payload and its signature,
then re-transmits them. To mitigate such attacks, Prepr includes a timestamp in
the `Prepr-Signature` header. Because this timestamp is part of the signed payload,
it is also verified by the signature, so an attacker cannot change the timestamp
without invalidating the signature. If the signature is valid but the timestamp is too old,
you can have your application reject the payload.
We recommend a tolerance of five minutes between the timestamp and the current time.
We advise that you use Network Time Protocol (NTP) to ensure that your server’s
clock is accurate and synchronizes with the time on Prepr servers.
Prepr generates the timestamp and signature each time we send an event to your endpoint.
If Prepr retries an event, for example: your endpoint previously replied with a non-2xx status code,
then we generate a new signature and timestamp for the next delivery attempt.
Source: https://docs.prepr.io/development/best-practices/webhooks
---
# Best practices for developers
*Check out our offering of best practices to develop streamlined and robust code for a headless CMS.*
Source: https://docs.prepr.io/development/best-practices
---
# Environment-to-environment content export
*This article explains how to export content from one environment to another,
for example, when you want to do system testing in your development environment with realistic content from the production environment.*
## Prerequisites
Make sure that the **Allow Developer actions** and **Allow bulk update content items and assets** permissions are enabled for users who need to export content.
The **Export to** action is available in the Content items list page for [*Developer*](/project-setup/managing-roles-and-permissions#developer) users only.
## Export content from one environment to another
The process below shows you how to export content from one environment to another in the same organization.
Source: https://docs.prepr.io/development/working-with-cicd/syncing-content
---
# Sync schemas between two environments
*This guide shows you how to update one schema with the other, for example, when you have a development and production environment with the same schema that is out of sync.*
## Introduction
In the event that you have separate environments, for example, *Development*, *Testing*, *Acceptance*, and *Production*, you may need to sync the schemas in these environments.
You can do this automatically by running a schema sync in Prepr.

There are a few schema sync processes you can choose from:
- [*GitHub schema sync*](#github-schema-sync) - Export to a GitHub repository or import from a GitHub repository to keep your schema in sync between environments.
- [*GitLab schema sync*](#gitlab-schema-sync) - Export to a GitLab project or import from a GitLab project to keep your schema in sync between environments.
- [*Bitbucket schema sync*](#bitbucket-schema-sync) - Export to a Bitbucket repository or import from a Bitbucket repository to keep your schema in sync between environments.
- [*Azure DevOps schema sync*](#azure-devops-schema-sync) - Export to a Azure DevOps repository or import from a Azure DevOps repository to keep your schema in sync between environments.
- [*Direct schema sync*](#direct-schema-sync) - Use this process to compare and sync your schema directly in Prepr between two environments in the same organization.
## Use cases
See the following use cases for when you may need to synchronize your schema:
- **Sync production to development**
An example *DTAP strategy* is to maintain the schema in the *Production* environment and when the same content structure is needed for realistic test cases this schema needs to be copied to the *Development*, *Test*, or *Acceptance* environment. Learn more on [how to set up a DTAP configuration](/project-setup/setting-up-environments#set-up-a-dtap-configuration).
- **Sync development to production**
After testing your development on the latest schema version in the *Development* or *Test* environments, it's possible that you may need to make changes to the live schema. In this case, you could copy these changes to the *Production* environment.
- **Share a schema between environments**
Use the *Shared schema* feature to maintain one schema across multiple environments, for example, for multiple brands. Check out more details in the [environments doc](/project-setup/setting-up-environments#environment-setup-for-multiple-brands).
A shared schema can also be imported from GitHub or GitLab or exported to GitHub or GitLab. This could be useful for agencies when you want to do testing with client's shared schema in your own organization instead of in the client organization.
## Prerequisites
You need to have a role, such as the *Developer* or *Admin* role, with the **Schema** permission enabled to have access to the **Sync schema** action.
If you need to sync a shared schema, then you need the organization **Schema** permission enabled.
Check out [roles and permissions in Prepr](/project-setup/managing-roles-and-permissions#add-or-edit-roles) for more details.
## GitHub schema sync
The process below shows you how to connect to your GitHub account and sync a schema using a GitHub repository.
## GitLab schema sync
The process below shows you how to connect to your GitLab account and sync a schema using a GitLab repository.
## Bitbucket schema sync
The process below shows you how to connect to your Bitbucket account and sync a schema using a Bitbucket cloud repository.
## Azure DevOps schema sync
The process below shows you how to connect to your Azure DevOps account and sync a schema using an Azure DevOps repository.
## Direct schema sync
Use the direct schema sync process if you choose not to use an external service like [*GitHub sync*](#github-schema-sync), [*GitLab sync*](#gitlab-schema-sync), [*Bitbucket sync*](#bitbucket-schema-sync), or [*Azure DevOps*](#azure-devops-schema-sync).
The direct schema sync needs no external tools, whereas using an external service gives you more flexibility and the added benefit of version control.
Note the list of constraints below before triggering the direct schema sync process.
### Constraints
- This process only works between environments in the same organization.
- Models in your target environment will be overwritten with the models from the current environment if their *Singular names* match.
Fields will be added or deleted to match the updated structure.
* Components, enumerations and remote sources in your target environment will be overwritten with the components, enumerations and remote sources from the current environment if their *Type name* values match.
Fields will be added or deleted to match the updated structure.
- Models, components, enumerations or remote sources that are not in the current environment, but present in the target environment will be deleted if they are unused.
The process below shows you how to sync your schema from one environment (*current environment*) to another (*target environment*).
If you have any questions about schema synchronization, please [reach out to our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/development/working-with-cicd/syncing-a-schema
---
# Validating Prepr schema JSON files
*This guide shows you how to validate schema JSON files using a GitHub validation action and the Prepr schema spec.*
## Introduction
In the event that you want to maintain your Prepr schema through a code editor, you can validate these schema JSON files before performing a [schema import](/development/working-with-cicd/syncing-a-schema#import-from-github).
## Validating your schema using the Prepr schema spec
Follow the steps below to [validate schema JSON files](https://github.com/preprio/action-schema-validation/tree/main) against the [Prepr schema spec](https://github.com/preprio/action-schema-validation/blob/main/spec/2026-03-05.json5).
If you have any questions or encounter issues with the [schema validation](https://github.com/preprio/action-schema-validation/tree/main), please [reach out to our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/development/working-with-cicd/validating-a-schema
---
# Manually exporting and importing a schema
*This guide shows you how to manually export and import parts of a schema, namely, models, components, enumerations and remote sources.*
## Introduction
In the event that you have separate environments and you want to copy parts of the schema from one environment to another, you can export and import the parts you need, namely, [a model](#export-and-import-a-model), [a component](#export-and-import-a-component), [an enumeration](#export-and-import-an-enumeration), or [a remote source](#export-and-import-a-remote-source).
## Export and import a model
Use the export and import if you only need a couple of models copied from one environment to another. For example, export a model from your staging environment and import the model into your production environment.
To sync a schema with models, components, enumerations and remote sources from another environment then follow the process detailed in the [Sync schema doc](/development/working-with-cicd/syncing-a-schema) instead.
To share the same models across multiple environments, for example when an organization has different brands, but needs content in separate environments, you can create a shared schema as detailed in the [Shared schemas doc](/project-setup/architecture-scenarios/shared-schema).
To export a model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Click a model from the list of models on the left.
3. Click the button at the top of the model.
4. Click **Export model** to download a JSON file of the model.

To import a model, follow these steps:
1. Click the **Schema** tab to open the *Schema Editor*.
2. Then, click the **+ Add model** button.
3. Click **Or import a model**.
4. Choose the JSON file of the model that you want to import. When you import a model, the settings are also copied across.

## Export and import a component
Use the export and import if you only need a couple of components copied from one environment to another. For example, export a component from your staging environment and import the component into your production environment.
To sync a schema with models, components, enumerations and remote sources from another environment then follow the process detailed in the [Sync schema doc](/development/working-with-cicd/syncing-a-schema) instead.
To share the same components across multiple environments, for example when an organization has different brands, but needs content in separate environments, you can create a shared schema as detailed in the [Shared schemas doc](/project-setup/architecture-scenarios/shared-schema).
To export a component, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click the component that you want to export from the list of components on the left.
3. Click the button at the top of the component.
4. Click **Export component** to download a JSON file of the component.

To import a component, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add component** button.
3. Click **Or import a component**.
4. Choose the JSON file of the component that you want to import.

## Export and import an enumeration
Use the export and import if you only need a couple of enumerations copied from one environment to another. For example, export an enumeration from your production environment and import it into your development environment.
To sync a schema with models, components, enumerations and remote sources from another environment then follow the process detailed in the [Sync schema doc](/development/working-with-cicd/syncing-a-schema) instead.
To share the same enumerations across multiple environments, for example when an organization has different brands, but needs content in separate environments, you can create a shared schema as detailed in the [Shared schemas doc](/project-setup/architecture-scenarios/shared-schema).
To export an enumeration, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click the enumeration that you want to export from the list of enumerations on the left.
3. Click the button at the top of the enumeration.
4. Click the **Export enumeration** option to download a JSON file of the enumeration.

When the export is successful, you'll find the JSON file in the location you selected.
To import an enumeration, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add enumeration** button.
3. Click the **Import enumeration** link.
4. Choose the JSON file of the enumeration that you want to import.

When the import is successful, you'll see the detailed enumeration in your schema.
## Export and import a remote source
Use the export and import if you only need a couple of remote sources copied from one environment to another. For example, export a remote source from your production environment and import it into your development environment.
To sync a schema with models, components, enumerations and remote sources from another environment then follow the process detailed in the [Sync schema doc](/development/working-with-cicd/syncing-a-schema) instead.
To share the same remote sources across multiple environments, for example when an organization has different brands, but needs content in separate environments, you can create a shared schema as detailed in the [Shared schemas doc](/project-setup/architecture-scenarios/shared-schema).
To export a remote source, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Click the remote source that you want to export from the list of remote sources on the left.
3. Click the button at the top of the remote source.
4. Click the **Export remote source** option to download a JSON file of the remote source.

When the export is successful, you'll find the JSON file in the location you selected.
To import a remote source, follow these steps:
1. Click the **Schema** tab to open the **Schema Editor**.
2. Then, click the **+ Add remote source** button.
3. Click the **Import custom source** link.
4. Choose the JSON file of the remote source that you want to import.
When the import is successful, you'll see the detailed remote source in your schema.
If you have any questions about exporting and importing parts of your schema, please [reach out to our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/development/working-with-cicd/exporting-and-importing-a-schema
---
# Working with CI/CD
Discover advanced Prepr CMS features designed to support your CI/CD processes.
Source: https://docs.prepr.io/development/working-with-cicd
---
# Managing content items
*Easily manage your content items by using some basic features in Prepr.*
## Introduction
This article shows you how to perform the following core tasks to manage your content:
- [Create a content item](#create-a-content-item)
- [Publish a content item](#publish-a-content-item)
- [Manage versions](#manage-versions)
- [Delete a content item](#delete-a-content-item)
- [Find your content items](#find-your-content-items)
## Create a content item
To add a new content item:
1. Go to the **Content** tab. Here you'll find a list of all content items in your Prepr environment.
2. Click the **Add item** button. The availability of models depends on the user role and permissions you have.

3. Select a model.
Now the content item is created and you can start composing your content.

The shown fields depend on the [model settings](/content-modeling/managing-models) in the *Schema*. The fields marked with an `*` are required.
### Guidance and help text
Depending on the setup of each field, you might see some help text for additional information about the content you need to create.

You might also see some additional highlighted guidance. Check out the [*Help text* field](/content-modeling/field-types#help-text-field) for setup details.

You can also create language variants of your content item in a multi-lingual setup. Check out the [Localization doc](/content-management/localizing-content#create-a-language-variant) for more details.
### Use AI to generate or optimize text
If enabled for your content text fields, you can **Ask AI** to generate or optimize content using different options.
- **Enter your own prompt**
You can enter your own prompt to generate new content or to adjust existing text like in the example below.

- **Optimize using quick actions**
You can simply choose from the list of available actions to request AI to optimize your text quickly.

- **Generate new text using predefined prompts**
You can choose to quickly generate text for a field based on a predefined prompt. For example, to generate an SEO title based on the main title of an article.

For setup details, check out the [AI text assistant guide](/ai-text-assistant#generating-text).
## Duplicate a content item
When you need to duplicate a content item, you can do so in a couple ways:
**From the Content items list**
1. Go to the **Content** tab.
2. Hover over the content item you want to duplicate to make the actions visible, and click the icon.

- **From the Content item page**
1. Go to the **Content** tab.
2. Click to open a content item you want to duplicate.
3. Click the button to open the drop-down list and choose the **Duplicate item** option.

## Publish a content item
When you're happy with the content item that you created and want it to be used and displayed in the web app, all you need to do is publish the content item.

You can publish a content item directly by clicking the **Publish** button or selecting one of the other publish options in the drop-down list,
or by using shortcut keys to publish the content item.
If your content item has any unpublished child content items, either content references or in a stack field,
you need to choose to either publish all content items including the unpublished child items or only the parent content item.

When you publish the content item directly, the *Workflow stage* of the content item is automatically changed to *Done*.
Check out the [Collaboration and workflows doc](/content-management/collaboration) for more details on workflow stages.
If you want to make changes to a published content item, edit the content item and click the icon to save the changes without publishing them.
This means there'll be two versions; your saved version and the published version.
You can then publish the saved version of the content item after making your changes.
## Schedule a content item
When you want content item changes to be published in the future, you can schedule a content item.
To schedule a content item, simply click to open the **Publish** actions list and click the **Schedule** option.

Then select the date and time for when you want the content item to be published by the web app. The content item will be marked as *Scheduled* until the date and time that you selected.
You can update the scheduled content item any time before the scheduled date and time without needing to reschedule the content item.
Check out the [Calendar view](#calendar) to get a clear overview of scheduled items.
To reschedule a content item in the calendar view, you can simply drag and drop the item to the desired date or click the icon and choose the **Reschedule** option.

Alternatively, you can click the **Reschedule** option in the content item when it's already published or scheduled.
When you reschedule a published content item, the *First Published at* date and time is set to the new date and time you choose.

This means you can order the content items in the front end when they're ordered by the *First Published at* date and time.
When you choose to schedule a content item, you can also enter the *Unpublish on* date and time for temporary content. When this date and time is reached the content item will be *Archived* and no longer available to the web app.
You can see all scheduled content items in the *List view* by filtering on the **Publication status** or the *Kanban view* by viewing the **Done** column for *Scheduled* items.
You can remove a schedule from a scheduled content item by opening the **Publish** drop-down list and choosing the **Remove schedule** option.
When you remove the schedule the workflow stage of the content item is set to *In progress*.

## Preview content items
You have a couple of options to preview content items to check what your content changes look like in the web app directly from Prepr.
### Visual Editing
You can click the icon to open Visual Editing.
Visual Editing is a live preview of a content item in a side-by-side view.
Simply save the content item to see your content changes instantly in this view.

When editing content in this live preview mode, you'll notice the following additional options:
- You can choose different screen sizes to get an accurate view of the page on different devices.

- When multiple URLs are enabled for Visual editing, for example, for test and development environments, you can switch to a different environment.

To set up Visual Editing, check out the [setup doc](/project-setup/setting-up-previews-and-visual-editing).
### Preview in a separate tab
Another option to preview your content item is to click the icon to open the preview in a separate tab.

To set up one or more preview URLs, check out the [setup doc](/project-setup/setting-up-previews-and-visual-editing).
## Manage versions
Content item versioning enables you to do the following tasks:
- Reset your last content item edits.
- Keep track of all the changes in a content item.
- Revert to a previous version when needed.
- Look for older versions in your content item.
- See every change made in a content item.
You can find content item versions in one of the following ways:
- Click the date link in the publish info text at the top left to open the activity log. And click the **Show earlier versions** link.

- Or click the button to open the drop-down list and choose the **Show version history** option.

You can then see the list of previous versions for this content item.

Select one of the versions and click **View** to open the specific content item version.
The yellow notification bar at the bottom of the page indicates that you are viewing an older content item version.
To restore a previous version, click **Restore** to open this version for further editing.
Take note of the following when viewing earlier versions:
- All fields and drag-and-drop elements are versioned.
- There is no version history of the slug and workflow settings.
- Content item versioning is available per locale.
## View linked content items
You can view all the linked items for a content item by following the steps below.

1. In the *Content* page, hover over the content item for which you want to view all linked items and click the icon.
2. Or open a content item and click the button to open the drop-down list and choose the **Show linked items** option.

3. In the updated filter criteria, you can choose either:
- **From**: to view all content items that includes this content item as a content reference.
- **To**: to view all content items this content item links to.
## Share a content item
You can share a content item with one or more users who have limited access to content items by following the steps below.
1. Go to the **Content** tab.
2. Click to open a content item you want to share.
3. Click the button to open the drop-down list and choose the **Share item** option.

4. In the pop-up window, choose one or more users you want to share the content item with and click the **Save** button.
Once shared, the users you selected will see the content item in their content item list.
## Delete a content item
When you need to delete a content item, you can do so in a couple ways:
**From the Content items list**
1. Go to the **Content** tab.
2. Hover over the content item you want to delete to make the actions visible, and click the icon.

3. In the pop-up window, click **Delete** again to confirm the action.
- **From the Content item page**
1. Go to the **Content** tab.
2. Click to open a content item you want to delete.
3. Click the button to open the drop-down list and choose the **Delete** option.

4. In the pop-up window, click **Delete** again to confirm the action.
### Delete a language variant
If you [manage multi-language content](/content-management/localizing-content), then you also have the option to delete a specific language variant. Click to delete a content item and then choose a preferred option:

### Recover a deleted item
You can recover a deleted item by clicking the *Deleted* view to list all the deleted content items.
Hover over the content item you want to recover and click the **Recover** link.

When the **Recover item?** pop-up window appears, click the **Yes, recover** button to confirm.
The restored content item no longer appears in the list of deleted items. Click the **All items** view to see your recovered item.
## Bulk actions on content items
In some cases you may want to perform certain actions on multiple content items at once.
To view and trigger bulk actions, go to the **Content** tab.
Hover over the content items and select the checkbox for the content items on which you want to perform the bulk action.

Let's look at each of these actions in more detail.
### Publish now or later
In some cases you might want to publish large batches of content items, such as campaigns or updates that span several pieces of content.
You can do this by following the steps below.

1. Choose the content items that you want to publish in the content item list.
2. Click the icon to **Publish now or later**.
3. Click the **Publish** button to publish the content items right away.
4. Or if you want to schedule the content items to be published later, choose a future date and time and click the **Schedule** button.
5. Check the number of content items in the confirmation message and click the **Yes, publish** button or the **Yes, schedule** button.
And that's it, you've published multiple content items simultaneously.
### Unpublish now or later
In some cases you might want to unpublish large batches of content items, such as outdated articles.
You can do this by following the steps below.

1. Choose the content items that you want to unpublish in the content item list.
2. Click the icon to **Unpublish now or later**.
3. Click the **Unpublish** button to unpublish the content items right away.
4. Or if you want to schedule the content items to be unpublished later, choose a future date and time and click the **Schedule** button.
5. Check the number of content items in the confirmation message and click the **Yes, unpublish** button or the **Yes, schedule** button.
And that's it, you've unpublished multiple content items simultaneously.
### Change workflow stage to
In some cases you might want to change the workflow stage of multiple content items, for example, when you've created a batch of content items and need to move them to the *Review* stage.
You can do this by following the steps below.
1. Choose the content items in the content item list for which you want to change the workflow stage.
2. Click the icon to **Change workflow stage to**.

3. Choose your desired stage, for example *Review* from the drop-down list.
And that's it, you've change the workflow stage for multiple content items simultaneously.
### Assign to
In some cases you might want to assign multiple content items to the same user, for example, when you've created a batch of content items and need someone to review them.
You can do this by following the steps below.
1. Choose the content items in the content item list you want to assign.
2. Click the icon to **Assign to**.

3. Choose the user from the drop-down list.
And that's it, you've assigned multiple content items to a single user simultaneously.
### Export to
In some cases it's necessary to export content from one environment to another, for example, when you want to do system testing in your development environment with realistic content from the production environment.

### Share
You can share multiple content items with specific users by following the steps below.
1. Choose the content items you want to share with a user, by hovering over each content item and clicking each checkbox.
2. To select all content items, click the icon at the top of the list.

3. Click the icon at the top of the list to share all the selected content items.
4. Choose the user you want to share the content items with.
Once shared, the user you selected will see the content items in their content item list.
### Delete
To delete multiple content items at once follow the steps below.
1. Choose the content items you want to delete, by hovering over each content item and clicking each checkbox.
2. To select all content items, click the icon at the top of the list.
3. Click the icon at the top of the list to delete all the selected content items.
4. In the pop-up window, click **Delete** again to confirm the action.

## Find your content items
There are several features listed below to help you find your content items easily.
### Layout options
You can use one of the layout options at the top of the *Content* page to find content items easily depending on your needs.
#### List
When you go to the **Content** tab, the *List* layout opens by default to show a complete list of all of your content items.
You can [filter the content items](#filter-options) to narrow down the list.

#### Calendar
Click the *Calendar* icon to get a clear visual overview of all your scheduled items to help you plan upcoming articles or campaigns to avoid overlap.

#### Kanban
Click the *Kanban* icon when you want to see a clear overview of content items by their status.

#### Content tree
Click the *Content tree* icon when you want to see an overview of the relationship between content items in a tree-like hierarchy.
The content tree is based on the slug value of content items.

If the content tree layout is not visible, request an admin user to [define the parent slug format](/project-setup/setting-up-environments#content-tree-parent-slug-format).
### Filter options
Prepr makes it even easier to filter precisely the content you're looking for with **Advanced Content Filters**.
You can choose multiple filters to narrow down your search for specific content items.
See the complete list of filters below.

#### Locale
You can filter content items by a specific locale, for example, `en-US` to select the list of American English versions of content.
#### Model
You can filter your content by one or more models. For example, to only list the blog posts or to view a list of pages and blog posts.
- *Supporting model* - When you choose a model, additional filters are available for all supporting models. You can choose from a list of models that are defined as content references, for example, the **Category** of the *Car* model.
- *Stack* - When you choose a model, additional filters are available for any stack fields defined in the model. The filter name matches the name of the [*Stack* field](/content-modeling/field-types#stack-field).
- *Enumeration* - When you choose a model, additional filters are available for any enumeration used in the model. The filter name matches the enumeration name, for example the **Size** for the *Product* model.
#### Publication status
You can filter your content by one or more publication statuses. For example, to list published and scheduled content items.
The possible filter values are *Published*, *Scheduled*, *Unpublished changes* and *Not Published*.
#### Workflow stage
You can filter your content by one or more workflow stages. For example, to list the content items that are in *To do* or *In progress*.
The possible filter values are *Done*, *Review*, *In progress*, *To do*, and *Archived*.
#### Assignee
You can filter your content by one or more assignees.
#### Publication date
You can filter content items by selecting a date range, including future dates, for example, to filter items scheduled to be published in the future.
#### Tags
You can filter content items by one or more tags.
### Views
You can see the list of views in the left sidebar of the **Content** page.

The following views are available out of the box and cannot be updated:
- *All items* - The default view of all content items without any filter criteria.
- *Scheduled* - The list of content items that are scheduled for a future date.
- *Experiments* - A view of all content items with adaptive content and A/B tests, in other words a complete list of all your experiments.
- *Needs attention* - The list of content items with broken links or could not be published. In this view, you can go to the relevant content
- *Deleted* - The list of deleted content items. In this view, you can recover content items that were deleted by mistake, for example.
Apart from these views, you can create your own *Shared view* or *Private view*.
A *Private view* will only be visible to you, while a *Shared view* will be visible to all users of this environment.
When you're viewing all content items and choose some filter criteria, these are retained for a session.
So the next time you visit the overview of the content items, the last selected filters will be active.
If you often use the same filters in your Prepr environment, you can save these as a view.
Make the filter selection and click the **Save as new view** link.
Give the view a name and save it. You can also group similar views by moving them into folders.

### Search content items
When you type any keyword in the search bar, Prepr performs a fuzzy search on the *Title* field of content items, by default.
When you click the search bar, a drop-down list appears with the following additional search options:
- *Search on full-text* - Prepr performs a fuzzy search on all text fields and text elements.
- *Search on slug* - Enter a partial or exact keyword for this fuzzy search on the *Slug field*.
- *Search on ID* - Enter the exact ID of the content item you are looking for.

Now that you understand each of the core tasks in more detail, review the handy shortcuts below to manage content.
## Manage content items with shortcut keys
You can use a number of shortcut keys to manage content items more efficiently.
Go to the **Content** tab to view the content item list.
From this view, you can use the following shortcuts:
- Add a new content item with . If the content item list is filtered by a specific model, this shortcut creates a content item for that model directly.
- Select all content items in the list with .
- Simply press the `esc` key to deselect any selected content items.
- Delete selected content items from the list by pressing the key.
Choose and click a content item in the list to make changes to that content item.
From a specific content item, you can use the following shortcuts:
- Publish the content item with .
- Save the content item with .
- Close the content item with .
From any other page in Prepr, you can press `/` to open the launchbar. The launchbar allows you to do quick searches on content items or any other page you have access to in your Prepr environment.
Source: https://docs.prepr.io/content-management/managing-content/managing-content-items
---
# Optimizing content for SEO with *Content check*
*This article explains how to optimize your content items for SEO (search engine optimization).*
## Search engine optimization
SEO (search engine optimization) is the practice of improving your website's content to increase visibility and ranking in a search engine's results with the goal of driving more relevant traffic.
You don't need to be an SEO expert to create high-ranking content in search engines when using Prepr.
When you want to create optimal SEO values, simply run the *Content check* feature while editing a content item.
## *Content check*
The [*Content check*](/content-management/reviewing-content#content-check) feature automatically checks a content item for nonoptimal SEO values and makes suggestions you can choose to apply, adjust or ignore.

For setup details, check out the [model settings](/ai-text-assistant#checking-seo-values).
## SEO values
Prepr checks the following SEO values when you run the *Content check* feature:
- SEO title
- Meta description
- Keywords
The SEO title and meta description values come from fields in a model or component that are marked as the **SEO title** and **Meta description**.
Check out the [field setup guide](/content-modeling/field-types#item-title-and-seo-fields) for more details.

In addition to checking the length of the SEO title and meta description, the *Content check* makes AI suggestions for alternative values
that meet the guidelines:
- SEO title - Recommended length between 50 and 60 characters
- Meta description - Recommended length between 120 and 160 characters
- Keywords - If keywords exist, they should exist in the SEO title or meta description, or they shouldn't be duplicated too many times in the SEO title or meta description.
## Adding keywords
When you run the *Content check* for a content item, and there are no keywords, you can add keywords to improve the SEO for this content item by clicking the **Add keywords** link.

For best practices on handling SEO for development, check out the [SEO best practices guide](/development/best-practices/seo).
Source: https://docs.prepr.io/content-management/managing-content/optimizing-content-for-seo
---
# Creating rich content
*Easily create rich content using basic features in Prepr.*
## Introduction
This article shows you how to enrich your content in different ways:
- Using the *Dynamic Content Editor* to add and format text, media, and structured elements.
- Adding content references to link related content items.
- Adding links to reference external resources.
- Adding remote content from external system.
- Working with a **Stack** field to build and manage the sections of a component-based page.
## Using the Dynamic content editor

In the *Dynamic Content Editor*, you can enrich your content using the following elements:
- **Paragraph**
The paragraph element contains a number of formatting options. You can use **bold text**, *italic*, and . Bullets (ordered and unordered), links, and tables are also available.
- **Headings**
Depending on the settings, you can choose up to six levels of headings to match your content item. You can easily change a heading element to another heading or other text element by making a selection from the drop-down options.
- **List**
You can add an ordered or unordered list. Like the paragraph, you can apply a number of formatting options.
- **Table**
You can add a table with up to 10 rows and 10 columns.
- **Code**
You can paste a code snippet in your content, for example, HTML.
- **Media**
Depending on the settings on the [dynamic content field](/content-modeling/field-types#dynamic-content-field), select the icon to add one or more images, videos or files. You could also edit captions, set the alignment or define image presets. [Learn more about image presets and alignment](/content-management/managing-assets/editing-and-configuring-assets).
- **Social post embeds**
Social elements are embeds from the largest social media platforms, namely, Twitter, Facebook, Instagram, Spotify, Youtube, SoundCloud, vimeo, TikTok, Apple Podcast, Bluesky, and Threads. To embed a social post, choose the corresponding social media icon and insert the URL of the social post.
- **Location**
Click the icon to add a location based on a Google Map address or coordinates.
- **Integrations**
If you need to reference content in an external CMS, a legacy system or an ecommerce platform, click the corresponding integration icon and choose the integrated content. Prepr will keep the data in sync automatically. Check out more details in the [integrations](/integrations) docs.
- **Component**
If a component is defined and included in the settings of the dynamic content field, you can embed a component by clicking on the corresponding icon. By using a component, you can add empty elements to your dynamic content. This way you can trigger the front end to insert static front-end components, such as banners, marketing widgets, or forms. Check out more details in the [components](/content-modeling/managing-components) doc.
There are several ways to edit your content in the *Dynamic Content Editor*:
- **Add an element.** To add a new element, do one of the following:
- Enter **/** to see a pop-up with all the elements.
- Click an element icon in the toolbar below.
- Press ENTER to add a new paragraph and SHIFT ENTER to add a break line.
- From the element icon on the left, click the **Add element below** button.
- **Multi-select *Text* elements.** You can select multiple text elements to copy or delete them.
- **Change the element type.** To change an element to another element type, click the element icon on the left and select the new element type from the drop down. Note that you can only change a filled element type to an element of the same type. For example, a paragraph can be changed to a heading, but not to a media file.
- **Sort elements.** To change the position of an element, click the element icon on the left and click the or button.
- **Remove elements.** To remove a text element from the dynamic content field, do one of the following:
- Empty the element and enter BACKSPACE.
- Click the element icon on the left and click the button.
## Adding content references
The content reference is a super-fast way to link related items to your content item. The content reference field is often used for linked content items, such as authors, categories, or as manually picked related items.
There are three options to display the content reference field, depending on your needs: as a modal window, as an autosuggest, or as checkboxes/radio buttons. You can make your choice in the [Reference field settings](/content-modeling/field-types#content-reference-field).
- **Modal window**. This option combines searching and adding new items. You can also add a filter to quickly find the related item you need. If the item you need is not present, you can create it there and then.

- **Auto-suggest**. You can use autosuggest if you need a specific content item quickly, without having to search extensively. Start typing in your input field and the results will appear immediately.

- **Checkboxes**. This option is best used if you only have a limited set of content items to choose from. An example is a topic or author you want to refer to. When you have indicated a maximum of 1 in your model, the options will be shown as a radio button.

## Adding internal and external links
### Dynamic internal links
The Internal links are references to items or assets in Prepr. You can refer to other content items or assets in a text editor or the *Dynamic Content Editor*.
To create an internal reference, click the **Insert item link** icon in the toolbar of the text editor and choose whether you want to link to a content item or an asset. Select the item or asset in the media browser and insert the link.

The created link is dynamic, this means that as soon as the title, slug, or URL of the linked content item or asset changes, the link will update dynamically. This way you never have to deal with dead links in your text again.
Check out the [dynamic content field API reference](/graphql-api/schema-field-types-dynamic-content-field#links-text) on how to query the internal link.
### External linking options
You can link to an external URL using the **Link option**.
If *Links* are enabled on a text field or the *Dynamic content Editor*, you can add an external link to your text.
To add an external link, click the icon to make an in-line reference. Enter the URL, and the link text, and decide whether this link needs to be opened in a new tab or not. For SEO purposes, you can also select the Don't follow option.

To edit the added link, simply click anywhere on the link text and click **Edit link** to make changes in the URL or in the link text.
## Adding remote content
When you need to add content that is sourced from an external system, and the [remote source is set up in Prepr](/content-modeling/creating-a-custom-remote-source),
you can simply you’ve connected to the external system and you can add the remote content to a content item using the following steps:
1. Navigate to the **Content** tab and click the desired content item from the list.
2. In the remote content section, click the button to add new items (in our example – **Add product**), search through the catalog or use a filter to find and add the desired items to the content item.
3. Save and publish this content item to complete the setup.

That’s it. Now your web page includes 3rd party content. Prepr will synchronize your remote content automatically to keep it up to date.
## Working with a Stack field
The *Stack* field is where you build and manage the sections of a component-based page.
You can see it in the example below as the *Content* section with a few components.

### Add a content item or component
Simply click the **+ Content item or component** button to add a new element to the stack.
This opens up a popup modal to help you find the content item or component you want to add.

### Reorder elements in a stack
To change the order of elements in the stack, click and hold the element, then drag it to the desired position and release.
The order you set here is the order in which the sections appear on the published page.
### Add an A/B test

Check out the [A/B testing guide](/ab-testing/running-ab-tests) for more details on A/B testing.
### Personalize an element

Check out the [Personalization guide](/personalization/managing-adaptive-content) for more details on personalizing content.
### Duplicate an element
When you need to make a quick copy of an element just below it, you can use the *Duplicate* option.
Simply, hover over the component or content item in the stack field and click the icon to duplicate the element.

### Copy an element
You can also copy an element to paste it to another stack field.
Hover over the component or content item in the stack field and click the icon to copy the element.
Go to the stack field where you want to place the component or content item and click the **Paste** button.

### Delete an element
To delete an element in the stack field, click the icon.

Then, click the **Yes, delete** button to confirm the deletion.
Source: https://docs.prepr.io/content-management/managing-content/creating-rich-content
---
# Managing content
*Learn about managing content items, improving SEO and readability and creating rich content in Prepr CMS.*
Source: https://docs.prepr.io/content-management/managing-content
---
# Introduction to Assets
*From this article, you’ll learn what assets are and which asset types are available in Prepr.*
## What are assets?
*Assets* are media files such as images, video, audio, and other digital files you can use on your web pages to enrich content.
With the growing demands of an internet business, brands have to do more to stand out and meet the rising expectations of potential customers. Rich content has a crucial role in attracting and keeping your audience.
Prepr allows you to easily create content pages filled with rich media assets. Let's look at them in detail.
## Assets in Prepr
An *Asset* in Prepr is a media file, such as an image, audio, video, live stream, or another file type, that lives in one organized storage — [*the Media Library*](/content-management/managing-assets/managing-assets). You only need to upload digital assets to Prepr once, and you can reuse them on multiple web pages.
Prepr automatically deploys and serves all media assets over a high-performing *Content delivery network (CDN)*. A CDN stores a cached version of content and puts it in many places simultaneously. This improves page load speed and accelerates content delivery.
Prepr supports the most popular image formats and provides automatic image optimization with *WebP encoding technology*. The WebP format makes your web pages load faster, frees up storage space, and guarantees high image quality.
The following table lists the asset types in Prepr and their supported media formats.
| Asset type|Description|Supported formats|
| -------------------------------------------- | ----------------------------------------------|---------------------|
| [Image](/content-management/managing-assets/introduction-to-assets#image) | Image files such as photos. | JPG, JPEG, PNG, BMP, SVG, GIF and more. **Note:** WebP graphics are supported through the *File* asset in Prepr. |
| [Video](/content-management/managing-assets/introduction-to-assets#video-and-audio) | Video files and live video streams. | MOV, M4V, MPG, MP4, and more. |
| [Audio](/content-management/managing-assets/introduction-to-assets#video-and-audio) | Audio files. | MP3, M4A, WAV, and more. |
| [File](/content-management/managing-assets/introduction-to-assets#file) | Archives, documents, and vector images. | The file upload recognizes the ZIP, PDF, XLSX, DOCX, and WebP extensions. Files with any other extension are uploaded as documents. |
### Image
There’s a wide range of image options in the Prepr Editor interface and through the API. For example, image presets for different device types, cropping, captions, and more.
Read more about [adding images to your web application](/development/best-practices/assets/images).
### Video and Audio
You can enrich your web pages with audio and video content or broadcast live video streams thanks to the *Prepr and Mux Video integration*. This integration gives you the benefits of seamless multi-channel content delivery and cutting-edge video processing technologies.
*Mux Video* accepts most modern video formats and codecs. And you get automatic video transcoding, audio normalization, and high-quality live streaming with the *HLS (HTTP Live Streaming)* protocol out of the box.
Prepr uploads your media files to Mux automatically, so you do not need to have a Mux account yourself. Follow our step-by-step guides to [post audio and video content](/development/best-practices/assets/video-audio) or [stream your live video](/development/best-practices/assets/live-video-stream) in a web application.
### File
With Prepr, you can add different file types to your web application, such as PDF, ZIP, and more. You can also use the *File* asset to work with WebP graphics in Prepr. Find more details in the [Files guideline](/development/best-practices/assets/files).
## Where to start?
To start with, learn how to create and where to store assets in Prepr. Please check out the [Manage assets](/content-management/managing-assets/managing-assets) guide.
Source: https://docs.prepr.io/content-management/managing-assets/introduction-to-assets
---
# Managing assets on the *Media* page
*This article explains where assets live in Prepr and which actions you can perform on them.*
## Introduction
You can find all your digital assets in one place in Prepr using the **Media** page.
This makes it easy to access, view, and manage your assets.
You can directly upload, edit, download, replace, delete, organize, and find assets from the *Media* page. For Mux videos, you can also replace thumbnails and add subtitles.
Let's dive into each of these actions in more detail.
## Uploading assets
To upload an asset, follow the steps below.

1. In the **Media** page, simply drag and drop the asset/s directly from your local drive.
2. Or at the top of the **Media** page, click the **Upload asset** button to open your file explorer and choose the asset/s you want to upload and click the **open** button.

You'll see the progress of asset/s in each thumbnail while they're uploading.
Once done, you can then click the thumbnail of the assets you want to view.
## Editing assets
To edit a specific asset, go to the **Media** tab and click the thumbnail of the asset to open it.
You can update some of the asset fields below directly in the *Asset* page.

- **Internal name** - This field is required and is used to enter the *Title* of the asset.
- **Description** - This optional field is used by editors to describe the asset.
- **Author** - This optional field can be used to enter the name of the photographer or the visual designer of the asset.
- *Additional fields* - Any additional fields can be added to the **Asset** model, such as **Copyright info**. For more details, check out the [Asset model doc](/content-modeling/defining-the-asset-model).
- **Tags** – Allows you to [add one or more tags to an asset](#tags).
- **Collections** – Allows you to [group assets into collections](#collections).
- **Used in** - The list of all content items that use this asset.
To edit the asset details, simply update the applicable fields and click the **Save and close** button.
In the sidebar of the *Asset* page, you can see the following generated data:
- **Optimized URL** – The URL you can use to include the asset in your front end.
- **Download URL** – The URL you can use to download an original asset file.
- **Asset ID** – The unique identifier of an asset you can refer to in API requests.
- **Playback ID** – The public Mux Playback ID for videos.
- **Metadata** – The date and time when an asset is created and changed.
### Multiple locale assets
When localization is enabled in the Asset model, additional fields will be available for each locale allowed in your environment.

For more details on the setup, check out the [Asset model doc](/content-modeling/defining-the-asset-model#enable-localization-in-assets).
## Downloading an asset
In the *Asset* page, you can download this asset to your local storage, by clicking the **Download** button in the sidebar.

The original file will be saved to your default download folder.
## Replacing an asset
In the *Asset* page, you can replace an existing file with a new one by clicking the **Replace** button to choose the replacement file from your local storage.

## Deleting assets
In the *Asset* page, you can delete an individual asset if you no longer need it, by simply clicking the **Delete** button.

In the confirmation dialog window, click the **Yes, delete** button to confirm the delete.
You can also delete multiple assets by following the steps below.
1. Go to the **Media** tab and select assets by hovering over each thumbnail and clicking the icon. To select all assets, click the icon in the top action menu.
2. In the top action menu, click the icon to delete the selected assets.

3. In the confirmation dialog window, click the **Yes, delete** button.
## Organizing assets
In Prepr, you can organize your assets in a way that works for you. For example, choose to group assets into collections, add tags, or do both for better categorization and precise search. Find out more below.
### Collections
A *Collection* represents a set of media files grouped by specific attributes and includes all types of assets.
It can be a collection of profile pictures, banners, stock photos, etc.
Let's look at how to organize assets using collections in more detail.
#### Create a collection of assets
To create a collection, follow the steps below.

1. Go to the **Media** page.
2. Under the *Collections* section on the left, click the **+ Add collection** link.
3. Enter the title for a collection and click the **Add** button to confirm.
4. You can then add assets to the collection in different ways:

a. From the *All assets* view select the images you want in the collection and drag them into the collection directly.
b. You can also add assets directly in the collection, by clicking the collection to open it. From here, click the **Add assets** button to open the *Media browser*.
c. In this window, you can then select and drag the assets you want to add to the collection or select the assets and click the **Add x assets** button.
d. If the images you want to add to the collection are not yet uploaded, click the **Upload assets** button to upload new assets and add them to the collection.
To add just a single asset to a collection in the asset detail page.
1. In the **Media** page, click the thumbnail of an asset to open it.
2. At the bottom of the *Asset* detail page, click the **+ Add to collection** link in the *Collections* section.
3. Choose a collection from the drop-down menu.
4. On the right-hand sidebar, click **Save and close** to apply the changes.
#### Remove assets from a collection
You can remove multiple assets from a collection or a single asset in the asset detail page.
To remove several assets from a collection, follow the steps below.
1. Go to the **Media** page and under the *Collections* section on the left, click the collection from which you want to remove assets.
2. Select the assets you want to remove by hovering and clicking the icon on each of the thumbnails.
3. Click the icon in the top action bar to remove the assets from this collection.

To remove a single asset from a collection, follow the steps below.
1. Go to the **Media** page and click the asset you want to modify.
2. On the *Asset* detail page, go to the **Collections** section and hover over the collection.
3. Click the icon to remove the asset from the collection.
4. On the right-hand sidebar, click **Save and close** to apply the changes.
#### Update the collection title
To update a collection title, under the *Collections* section on the left, hover over the collection you want to edit.
Then click the icon and choose the **Edit collection** option.

In the dialog window, enter a new title and click the **Save** button.
#### Delete a collection
To delete a collection title, under the *Collections* section on the left, hover over the collection you want to delete.
Then, click the icon and choose the **Delete collection** option. In the confirmation window, click the **Yes, delete** button.
### Tags
A *Tag* is a meaningful label you can assign to your assets to differentiate between them while filtering.
You can add tags to multiple assets or to a single asset in the *Asset* page.
To tag several assets at once, follow the steps below.
1. Go to the **Media** page and in the *All assets* view, select the assets you want to tag by hovering over each thumbnail and clicking the icon.
2. In the top action menu, click the icon.

3. Choose an existing tag or type a new one.
4. Click the **Add tags** button.
To tag a single asset, follow the steps below.
1. Go to the **Media** tab and click the asset to open it.
2. At the bottom of the **Asset** page, click the *Tag* field.
3. Choose an existing tag or type a new one.
4. On the right-hand sidebar, click the **Save and close** button to apply the changes.
That’s it. You have organized your assets in the Media Library. Now it's easier to find the right one. Let's see how to do this in the next paragraph.
## Finding assets
Prepr gives you multiple search and filter options to find the assets you need with ease:
- Search the assets by the asset title or keywords in the asset description.
- Filter the assets by *Asset type*, *Tags*, *Created on* date, or the *Unused* checkbox.
- You can also filter assets by [enumeration field values in the Asset model](/content-modeling/defining-the-asset-model#add-fields-to-the-asset-model), for example, to find a specific license holder.

## Replacing video thumbnails
By default, a thumbnail image is taken from the middle of the video based on the Mux *Playback ID*. Still, you can set a custom video thumbnail in Prepr as follows:
1. Go to the ** Media** page and click the thumbnail of the video asset that you want to edit.
2. On the asset detail page, hover over the thumbnail and click the icon.
3. Choose the replacement image file to replace the thumbnail.

## Adding video subtitles
You can add subtitles to your Mux videos in Prepr to provide multilingual support to your web app visitors and extend the web app accessibility.
To add subtitles, follow the steps below.
1. Go to the ** Media** page and open the video file to which you want to add subtitles.
2. On the asset detail page, click the **+ Add Subtitle** link.
3. Choose a locale, and upload either an *SRT* or *WebVTT* file containing the subtitle information.
Prepr will automatically generate a subtitle URL and add it to the video in Mux.

There's no limit on the number of subtitle files you can include in your video asset. Each subtitle file will be stored as an individual file asset in Prepr.
[Learn more on how to add videos to your web app](/development/best-practices/assets/video-audio).
Source: https://docs.prepr.io/content-management/managing-assets/managing-assets
---
# Using assets in content items
*This guide describes the Prepr features you can use to edit and configure assets when adding them to your web application.*
## Introduction
Media files can enrich your website content only when they are qualitative and usable. For instance, images should be optimized for a device and presented at the highest resolution possible. Likewise, having a well-described video with the key cues in your audience’s native language increases the chances your visitors will watch the content. In other words, the way you configure your assets significantly affects your content usability.
The following options are available for you to edit and configure assets in your content item:
- Adding captions
- Setting alignment
- Setting an image focal point
- Cropping images
- Setting a download filename
Before configuring your assets though, let's first look at how to add assets to a content item.
## Adding assets to a content item
If [asset fields are defined in the model](/content-modeling/field-types#assets-field) of your content item, you'll see an asset block for each asset field.
Check out more details on [the supported assets in Prepr](/content-management/managing-assets/introduction-to-assets#assets-in-prepr).
To add assets to a content item, simply drag and drop the asset file from your local storage to this block, or click it to open the media window dialog to choose an existing file in Prepr.

From this window, you can also click the **Upload asset** button instead to choose the files you want to upload and add them to your content item.
Once done, click the **Add x assets** button to add these to the content item.
Now that you know how to add assets to a content item, let's dive into different display options for the images.
## Setting alignment
The *Alignment* feature lets you position a digital asset within the text elements on a web page. You can choose from the following options:
- **Left** – aligns the left edges of an asset and text element.
- **Center** – aligns the center points of an asset and text element.
- **Right** – aligns the right edges of an asset and text element.
To align an asset, a developer can [enable the *Allow alignment* toggle in the asset field settings](/content-modeling/field-types#assets-field-settings).
1. On the **Content item** page, hover over an image and click the icon in the top right corner.
2. Select the desired alignment option.

## Adding captions
The *Caption* feature lets you create multiple descriptions for a single asset every time you use this asset in a content item or a content item locale. While the [*Asset Description* field](/content-management/managing-assets/managing-assets#editing-assets) is recommended for internal use, captions are helpful in the following cases:
- When you need to describe a single asset across multiple content items.
- When you need to describe a single asset in a multi-language content item.
To create a caption for an asset, a developer can [enable the *Allow caption* toggle in the asset field settings](/content-modeling/field-types#assets-field-settings).
1. On the **Content item** page, hover over the image thumbnail and click the icon in the top right corner, then click **Edit caption**.

2. In the opened dialog window, type your text, then click **Save** to apply your caption.
## Setting an image focal point
An image focal point is the key area that you want to remain visible regardless of how the image is resized.
It ensures that important parts of the image (such as a person’s face or a product) stay in focus when displayed across different screen sizes or aspect ratios.
If the [set image focal point option is enabled](/content-modeling/field-types#assets-field-settings), you can choose the focal point for an image when adding or editing the image in a content item .
### Set focal point for new image
Follow the same process above to [add images to a content item](#adding-assets-to-a-content-item).
When adding an image to a content item and a focal point is required in the [asset field settings](/content-modeling/field-types#assets-field), you'll see the **Set focal point** button at the bottom of the *Media* dialog window.
1. Click the **Set focal point** button to open the *Set focal point* modal.

2. Move the focal point indicator to the part of the image you always want visible and click the **Add 1 asset** or the **Next asset** (in the case of multiple assets) button.
3. When you've set the focal image for multiple images, simply click the **Add x assets** button.
### Set focal point for existing image
1. On the **Content item** page, hover over the image and click the icon in the top right corner to open the *Set focal point* modal.
2. Choose the focal point for the selected image and click the **Save** button.

## Cropping images
*Cropping* an image is important when adding it to your content.
You crop images to look great on all devices, such as mobile, desktops, and tablets.\
With Prepr, you can crop image files easily, without altering the original stored files.
To enable cropping on images, developers can define the image presets. Check out the [*Assets* field settings](/content-modeling/field-types#assets-field) for more details.
When you add images to content items and crop them according to the presets, a developer can retrieve the chosen presets using the GraphQL API to render them accordingly in the web app.
For that, they need to specify a preset name in the API request. For more details, see the sample query for retrieving *Presets* in the [GraphQL API reference](/graphql-api/schema-field-types#images).
Alternatively, a developer can transform images themselves using the API. For more information, check out the [REST API](/mutation-api/assets-resizing) or the [GraphQL API](/graphql-api/schema-field-types#images).
### Crop new images
Follow the same process above to add images to a content item.
When adding an image to a content item and cropping is required in the [asset field settings](/content-modeling/field-types#assets-field), you'll see the **Crop x assets** button at the bottom of the *Media* dialog window.

1. Click the **Crop x assets** button to open the crop images window.

2. Simply choose the preset and move the crop area over the part of the image that you want cropped.
3) If there are multiple presets, simply click the thumbnail of the next preset to add its crop.
4) When cropping multiple files, click the next image thumbnail in the bottom left corner to crop another image or click the **Next asset** button.
5) Once done, click the **Add x assets** button to add the cropped images to the content item.
### Crop existing images
When cropping is not set to required, you can add a crop after the image has already been added to a content item.
To crop an existing image in a content item, complete the following steps for each image:
1. On the **Content item** page, hover over the image and click the icon in the top right corner, then click **Crop image**. Or click the icon on the image thumbnail to open the crop editor quickly.

2. In the crop editor window, select a preset and an area in your crop by dragging the points or moving the entire crop area.
3. Click **Save** to apply the updated cropped image.

### Edit image crop
If you want to change the current crop, complete the following steps for each image:
1. On the **Content item** page, hover over the image and click the icon in the top right corner, then click **Edit crop**. Or simply click the icon to open the crop editor window and follow the same steps as above.

### Delete image crop
To delete a crop, hover over the image thumbnail in the *Content item* and click the icon in the top right corner.
Then, choose the **Delete crop** option.

## Setting a download filename
When you need to provide a download URL link in your content item to make a file available to web app visitors for download,
you can set a parameter in the URL to define the filename.
For example: `https://acme-company.files.prepr.io/24eovl6bs4q-guide.pdf?filename=the-best-guide-ever.pdf`
If you don't set this filename parameter, the filename in the URL is used as is. In the example above, this would be `24eovl6bs4q-guide.pdf`.
Source: https://docs.prepr.io/content-management/managing-assets/editing-and-configuring-assets
---
# Managing assets
*Learn how to upload, manage and store your assets centrally in Prepr CMS to create photo galleries, video blogs, or even start a live-streaming event in your web application.*
Source: https://docs.prepr.io/content-management/managing-assets
---
# Add Exif data to images automatically
*This integration allows you to add Exif data to your images automatically.*
## Introduction
Exif (Exchangeable image file format) is a standard that specifies formats for media files. When you activate the *Prepr image processing* integration, you can choose which Exif data to add to your image assets.
## Activate Exif
Simply activate the Exif integration with the following steps:

1. Click the icon and choose the **Integrations** option to view all integrations.
Go to the **Prepr image processing** card and click the **Activate** button.
2. Choose the **Exif** option.
3. Select one or more of the fields you want to extract:
- **Internal name**
- **Description**
- **Author**
4. Click the **Save** button.
5. To make any changes to the selected fields, go back to the **Prepr image processing** card and click the **Manage** button to make your changes.
Now that the integration with Exif is activated, your chosen fields will be filled automatically whenever new images are uploaded.
If you have any questions, please [contact our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/integrations/image-processing/exif
---
# AI-generate text for images
*The AI integration automatically generates text values, such as alt text, when uploading new images.*
## Introduction
When you activate the *Prepr image processing* integration, you can choose to integrate to AI to process images.
When a user uploads an image, Prepr CMS prompts AI to scan the image and the image file name to detect information and generate selected text values, such as alt text.
## Activate AI generation for image text fields
Simply activate the AI integration with the following steps:

1. Click the icon and choose the **Integrations** option to view all integrations.
Go to the **Prepr image processing** card and click the **Activate** button.
2. Enable the **AI** toggle and choose one or more of the fields listed.
- **Internal name**
- **Description**
- **Custom asset fields** - These include any custom text fields you added to the [*Asset* model](/content-modeling/defining-the-asset-model#add-fields-to-the-asset-model), like an *Alt text* field.
To make changes to the options, go back to the **Prepr image processing** card and click the **Manage** button to make your changes.
3. If you have any custom asset fields in the *Asset* model, set a prompt for each text field you want AI-generated, as follows:
- Go to the **Schema** page and click to open the **Asset** model.
- Click the text field you want AI-generated and in the dialog, click the *AI* tab.
- Then enable the toggle to **Allow AI text generation**
- Choose one of the *Suggested prompts* and edit it, if needed, or create your own and click the **Save** button.
Once the AI integration is activated and all prompts are set, editors will get generated text for any new images they upload.

If you have any questions, please [contact our Support team](https://prepr.io/support).
Source: https://docs.prepr.io/integrations/image-processing/ai
---
# Prepr image processing
Prepr CMS can automatically process images on upload, enriching chosen text values in your image assets to help boost SEO.
You can activate this integration to either pull details from Exif data or to let AI generate alt text or other text values for images.
Source: https://docs.prepr.io/integrations/image-processing
---
# Connect Claude Desktop to the Prepr MCP server
*Add Prepr as a custom connector in Claude Desktop, authorize access to your environment, and test the connection.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/claude-desktop
---
# Connect Claude Code to the Prepr MCP server
*Add Prepr as a remote HTTP MCP server in Claude Code, authorize access, and test the connection from your project.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/claude-code
---
# Connect ChatGPT to the Prepr MCP server
*Add Prepr as a custom MCP app in ChatGPT, authorize access to your environment, and test the connection.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/chatgpt
---
# Connect Codex to the Prepr MCP server
*Add Prepr as a streamable HTTP MCP server in Codex, authorize access to your environment, and test the connection.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/codex
---
# Connect Cursor to the Prepr MCP server
*Add Prepr to Cursor's MCP configuration, authorize access to your environment, and test the connection.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/cursor
---
# Connect GitHub Copilot CLI to the Prepr MCP server
*Add Prepr to GitHub Copilot CLI's MCP configuration, configure authentication, and test the connection.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/github-copilot
---
# Connect a Notion custom agent to the Prepr MCP server
*Add Prepr as an MCP connection for a Notion custom agent, authorize access, and test the connection.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/notion-custom-agent
---
# Connect OpenCode to the Prepr MCP server
*Add Prepr to OpenCode's MCP configuration, configure authentication, and test the connection.*
## What’s next?
Explore [typical use cases](/prepr-mcp-server/use-cases) or review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions).
Source: https://docs.prepr.io/prepr-mcp-server/getting-started/opencode
---
# Get started with the Prepr MCP server
*Connect your preferred AI client to the Prepr MCP server, then test the connection with your Prepr content.*
## Before you start
All clients connect to the same remote MCP server:
- *Server URL:* `https://mcp.prepr.io`
- **Authentication:** OAuth, when supported by the client
## Choose your AI client
Review the [available tools and actions](/prepr-mcp-server/available-tools-and-actions) and [typical use cases](/prepr-mcp-server/use-cases) before you start building workflows.
Source: https://docs.prepr.io/prepr-mcp-server/getting-started
---
# Introduction to the Next.js complete guide
*This section introduces you to the complete guide that shows you how to connect Prepr to a Next.js project including styling, adaptive content, and A/B testing and a preview bar.*
At the end of the complete guide, you’ll have a working website with personalization and A/B testing like the images below.
You can customize this project to fit the requirements for your web app.
**Home page for general website visitors**

**Two variants of a running A/B test on a landing page**

**Personalized home page for electric car lovers**

## Prerequisites
Make sure the following is in place before getting started:
- [A free Prepr account](https://signup.prepr.io/?plan=free)
- [An environment with Acme Lease demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en/download)
Now that you know what to expect and have your Prepr environment ready, you can get started with the first step to [set up the Next.js project](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-1-set-up-a-nextjs-project).
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide/introduction
---
# Set up a Next.js project
*The instructions below will guide you through the first step of the Next.js complete guide.
These show you how to create a Next.js website with static components.*

You can also watch the video for step-by-step instructions detailed in the guide below.
## Create a Next.js website with static components
If you have an existing Next.js project then you can skip this section and continue with the next section to [make your Next.js project dynamic with Prepr content](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-2-make-the-project-dynamic).
Otherwise, let's get started.
Great work on getting your static web app working!
Continue your journey to the next section to [make your Next.js project dynamic with Prepr content](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-2-make-the-project-dynamic).
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide/step-1-set-up-a-nextjs-project
---
# Make your Next.js project dynamic
*This guide shows you how to connect an existing Next.js project to Prepr to retrieve and show Acme Lease demo content.*
{/* installtion of code generator is different than described in the video
You can also watch the video for step-by-step instructions detailed in the guide below.
*/}
## Connect your Next.js website to Prepr
The steps below continue from the previous section, [Set up a Next.js project](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-1-set-up-a-nextjs-project).
If you don't yet have a Next.js website with static pages, follow the steps in this section first.
Otherwise, let's get started.
Congratulations! You have successfully connected your front end to Prepr to make your website dynamic.
Continue your journey to the next section to [set up data collection](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-3-set-up-data-collection).
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide/step-2-make-the-project-dynamic
---
# Set up data collection with tracking and the Prepr Toolkit
*Prepr is a CMS that includes built-in A/B testing and personalization features.
These capabilities require visitor data to measure your test results and to build segments for personalization.
This chapter of the Next.js complete guide shows you how to enable tracking to collect this data in your Next.js front end and how to simplify this data collection with the Prepr Toolkit.*
{/* Excluded because we explicitly mention the Prepr Next.js package
You can also watch the video for step-by-step instructions detailed in the guide below.
*/}
The steps below continue from the previous section, [Make your Next.js project dynamic](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-2-make-the-project-dynamic).
If you don't yet have a Next.js project connected to Prepr, follow the previous steps listed in the [Complete guide overview](/connecting-a-front-end-framework/nextjs/next-complete-guide) to create one.
Otherwise, let's get started.
## Install the Prepr Toolkit
We developed the [Prepr Toolkit](https://github.com/preprio/prepr-toolkit) to simplify working with event data for A/B testing and personalization.
In the steps below, you'll enable tracking in your website by adding the *Prepr Tracking Code* to your front end.
This code generates the `__prepr_uid` cookie for each website visitor.
This visitor gets stored in Prepr with this unique ID, if they don't already exist.
This means you can use this `__prepr_uid` cookie value to set the API request header, `Prepr-Customer-Id`, when you retrieve the page content for A/B testing and personalization.
The *Prepr Toolkit* prepares the `Prepr-Customer-Id` API request header for you, using the `__prepr_uid` cookie value.
Based on this value, Prepr retrieves the right content as follows:
- A/B testing: Prepr decides which variant, A or B, to return in the content.
- Personalization: Prepr checks the segment that this visitor belongs to and retrieves the matching personalized variant.
To install the *Prepr Tookit*, follow the steps below.
1. In your Next.js project, stop the localhost website server (`CTRL-C`) if it's running and install the package with the following terminal command:
```bash copy
npm install @preprio/toolkit
```
Once done, you need to call a function in the package to prepare the API request headers before the page gets rendered in your website.
2. To do this, go to your project and create a new file `proxy.ts` in the `src` folder. Then add the following code to perform the package's preprocessing logic on user requests.
```ts filename="./src/proxy.ts" copy
import type { NextRequest } from 'next/server'
import { createPreprMiddleware } from '@preprio/toolkit/nextjs'
export function proxy(request: NextRequest) {
return createPreprMiddleware(request, { preview: false })
}
```
## Enable Prepr tracking and collect view events
Congratulations! You've successfully enabled Prepr tracking in your Next.js front end, started collecting page view events, and installed the Prepr Toolkit for A/B testing and personalization.
Now, you're ready to [add A/B testing](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-4-add-ab-testing) and [personalization](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-5-add-personalization) to your website.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide/step-3-set-up-data-collection
---
# Add A/B testing to your Next.js website
*Prepr CMS enables editors to create A/B tests directly within their content.
This means testing different content versions is simple and effective.
This chapter of the Next.js complete guide demonstrates how to set up A/B testing in Prepr, including testing the display of variants and measuring the results.*
At the end of this section, you'll see the A and B versions on the *Electric Lease Landing Page*.

The steps below make use of the A/B test in the *Electric Lease Landing Page* content item from the Acme Lease demo data.

This chapter continues from the previous section, [Set up data collection](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-3-set-up-data-collection).
If you haven't yet enabled Prepr tracking in your Next.js project, follow the steps in this section first.
Otherwise, let's get started.
{/* Removed because we explicitly mention the Prepr Next.js package
You can also watch the video for step-by-step instructions detailed in the guide below.
*/}
## Set up A/B testing for the *Electric Lease Landing Page*
You can run an A/B test on parts of a web page to show two different versions of the content to different visitors and compare which variant is more engaging.
To show the right variant to the right visitor:
- Every visitor gets an ID that resolves to an A or a B variant.
- In your front end, you need to send this ID along with the query to retrieve the right variant.
- This is done by setting the value for the API request header, `Prepr-Customer-Id`, when you make the API request to retrieve the content.
By [installing the Prepr Toolkit](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-3-set-up-data-collection#install-the-prepr-toolkit) in the previous section, you've already prepared your API request headers.
Congratulations! You have a running A/B test with metrics in your Next.js website.
Now, you can [Add personalization](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-5-add-personalization) to your website.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide/step-4-add-ab-testing
---
# Add personalization to your Next.js website
*Prepr CMS enables editors to create adaptive content directly in their content items to personalize elements of web pages.
This makes setting up personalization really simple and effective in increasing engagement and conversions.
This chapter of the Next.js complete guide shows you how to add personalization to your Next.js website.*
At the end of this section, you'll see a personalized home page for visitors interested in electric cars.

The steps below make use of the Adaptive content in the *Homepage* content item from the Acme Lease demo data.

This chapter continues from the previous section, [*Set up data collection*](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-3-set-up-data-collection).
If you haven't yet enabled Prepr tracking in your Next.js project, follow the steps in this section first.
Otherwise, let's get started.
{/* Removed video because we explicitly mention the Prepr Next.js package
You can also watch the video for step-by-step instructions detailed in the guide below.
*/}
## Set up personalization for the home page
You can show different personalized versions of the content to different groups (*Segments*) of visitors.
To show the right variant to the right visitor:
- Every visitor is given an ID that resolves to a segment they belong to.
- In your front end, you need to send this ID along with the query to retrieve the right variant.
- This is done by setting the value for the API request header, `Prepr-Customer-Id`, when you make the API request to retrieve the content.
By [installing the Prepr Toolkit](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-3-set-up-data-collection#install-the-prepr-toolkit) in the previous section, you've already prepared your API request headers.
Congratulations! You have successfully set up personalization with metrics in your Next.js website.
Now, you can [install the preview bar](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-6-install-the-preview-bar) to your website.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide/step-5-add-personalization
---
# Install the *Prepr preview toolbar*
*This guide shows you how to install the Prepr preview toolbar to easily review A/B testing and Adaptive content directly in your Next.js website.*
At the end of this section, you can use the Prepr preview toolbar in your website.

{/* Removed because we explicitly mention the Next.js package
You can also watch the video for step-by-step instructions detailed in the guide below.
*/}
## Add the *Prepr preview toolbar* to your website
The steps below assume that you already have [A/B testing](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-4-add-ab-testing) or [personalization](/connecting-a-front-end-framework/nextjs/next-complete-guide/step-5-add-personalization) set up in your Next.js website.
If you haven't yet added A/B testing or personalization, follow the steps in these sections first.
Otherwise, let's get started.
## All done
Congratulations!
You have successfully installed the Prepr preview toolbar in your Next.js website.
This brings you to the end of the Next.js complete guide which has given you all the tools and tips you need to create your own web app
connected to Prepr CMS complete with personalization, A/B testing and a preview bar.
Don't hesitate to [give us feedback](mailto:feedback@prepr.io) on your experience using this guide.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Content modeling examples](/content-modeling/examples)
- [More data collection details](/data-collection)
- [More about A/B testing](/ab-testing)
- [More about personalization](/personalization)
- [Deploy your Next app with Vercel](https://nextjs.org/learn-pages-router/basics/deploying-nextjs-app)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide/step-6-install-the-preview-bar
---
# Complete guide to Next.js and Prepr
*This guide shows you how to connect Prepr to a Next.js project including styling, adaptive content, A/B testing and a preview bar.*
Follow the steps below in the recommended order to set up your own working Next.js website with adaptive content, A/B testing and a preview bar.
You can also customize this project to fit the requirements for your web app.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nextjs/next-complete-guide
---
# Introduction to the Nuxt complete guide
*This section introduces you to the complete guide that shows you how to connect Prepr to a Nuxt project including styling, adaptive content, A/B testing and the visual editing setup.*
At the end of the complete guide, you’ll have a working website with personalization and A/B testing like the images below.
You can customize this project to fit the requirements for your web app.
**Home page for general website visitors**

**Two variants of a running A/B test on a landing page**

**Personalized home page for electric car lovers**

## Prerequisites
Make sure the following is in place before getting started:
- [A free Prepr account](https://signup.prepr.io/?plan=free)
- [An environment with Acme Lease demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
- [The latest version of Node.js](https://nodejs.org/en/download)
Now that you know what to expect and have your Prepr environment ready, you can get started with the first step to [set up the Nuxt project](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-1-set-up-a-nuxt-project).
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/introduction
---
# Set up a Nuxt project
*The instructions below will guide you through the first step of the Nuxt complete guide.
These show you how to create a Nuxt website with static components.*

## Create a Nuxt website with static components
If you have an existing Nuxt project then you can skip this section and continue with the next section to [make your Nuxt project dynamic with Prepr content](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-2-make-the-project-dynamic).
Otherwise, let's get started.
Great work on getting your static web app working!
Continue your journey to the next section to [make your Nuxt project dynamic with Prepr content](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-2-make-the-project-dynamic).
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-1-set-up-a-nuxt-project
---
# Make your Nuxt project dynamic
*This guide shows you how to connect an existing Nuxt project to Prepr to retrieve and show Acme Lease demo content.*
## Connect your Nuxt website to Prepr
The steps below continue from the previous section, [Set up a Nuxt project](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-1-set-up-a-nuxt-project).
If you don't yet have a Nuxt website with static pages, follow the steps in this section first.
Otherwise, let's get started.
Congratulations! You have successfully connected your front end to Prepr to make your website dynamic.
Continue your journey to the next section to [set up data collection](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-3-set-up-data-collection).
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-2-make-the-project-dynamic
---
# Set up data collection with tracking
*Prepr is a CMS that includes built-in A/B testing and personalization features.
These capabilities require visitor data to measure your test results and to build segments for personalization.
This chapter of the Nuxt complete guide shows you how to enable tracking to collect this data in your Nuxt front end.*
## Enable Prepr tracking and collect view events
The steps below continue from the previous section, [Make your Nuxt project dynamic](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-2-make-the-project-dynamic).
If you don't yet have a Nuxt project connected to Prepr, follow the previous steps listed in the [Complete guide overview](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide) to create one.
Otherwise, let's get started.
Congratulations! You've successfully enabled Prepr tracking in your Nuxt front end, and started collecting page view events.
Now, you're ready to [add A/B testing](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-4-add-ab-testing) and [personalization](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-5-add-personalization) to your website.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-3-set-up-data-collection
---
# Add A/B testing to your Nuxt website
*Prepr CMS enables editors to create A/B tests directly within their content.
This means testing different content versions is simple and effective.
This chapter of the Nuxt complete guide demonstrates how to set up A/B testing in Prepr, including testing the display of variants and measuring the results.*
At the end of this section, you'll see the A and B versions on the *Electric Lease Landing Page*.

The steps below make use of the A/B test in the *Electric Lease Landing Page* content item from the Acme Lease demo data.

This chapter continues from the previous section, [Set up data collection](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-3-set-up-data-collection).
If you haven't yet enabled Prepr tracking in your Nuxt project, follow the steps in this section first.
Otherwise, let's get started.
## Set up A/B testing for the *Electric Lease Landing Page*
You can run an A/B test on parts of a web page to show two different versions of the content to different visitors and compare which variant is more engaging.
To show the right variant to the right visitor:
- Every visitor gets an ID that resolves to an A or a B variant.
- In your front end, you need to send this ID along with the query to retrieve the right variant.
- This is done by setting the value for the API request header, `Prepr-Customer-Id`, when you make the API request to retrieve the content.
Congratulations! You have a running A/B test with metrics in your Nuxt website.
Now, you can [Add personalization](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-5-add-personalization) to your website.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-4-add-ab-testing
---
# Add personalization to your Nuxt website
*Prepr CMS enables editors to create adaptive content directly in their content items to personalize elements of web pages.
This makes setting up personalization really simple and effective in increasing engagement and conversions.
This chapter of the Nuxt complete guide shows you how to add personalization to your Nuxt website.*
At the end of this section, you'll see a personalized home page for visitors interested in electric cars.

The steps below make use of the Adaptive content in the *Homepage* content item from the Acme Lease demo data.

This chapter continues from the previous section, [Set up data collection](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-3-set-up-data-collection).
If you haven't yet enabled Prepr tracking in your Nuxt project, follow the steps in this section first.
Otherwise, let's get started.
## Set up personalization for the home page
You can show different personalized versions of the content to different groups (*Segments*) of visitors.
To show the right variant to the right visitor:
- Every visitor is given an ID that resolves to a segment they belong to.
- In your front end, you need to send this ID along with the query to retrieve the right variant.
- This is done by setting the value for the API request header, `Prepr-Customer-Id`, when you make the API request to retrieve the content.
Congratulations! You have successfully set up personalization with metrics in your Nuxt website.
This brings you to the end of the Nuxt complete guide which has given you all the tools and tips you need to create your own web app
connected to Prepr CMS complete with personalization, and A/B testing.
Don't hesitate to [give us feedback](mailto:feedback@prepr.io) on your experience using this guide.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Set up visual editing](/project-setup/setting-up-previews-and-visual-editing)
- [More data collection details](/data-collection)
- [More about A/B testing](/ab-testing)
- [More about personalization](/personalization)
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-5-add-personalization
---
# Complete guide to Nuxt and Prepr
*This guide shows you how to connect Prepr to a Nuxt project including styling, adaptive content, and A/B testing.*
Follow the steps below in the recommended order to set up your own working Nuxt website with adaptive content, A/B testing and a preview bar.
You can also customize this project to fit the requirements for your web app.
Source: https://docs.prepr.io/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide
---
# Introduction to the Laravel complete guide
*This section introduces you to the complete guide that shows you how to connect Prepr to a Laravel project including styling, adaptive content, A/B testing and the visual editing setup.*
At the end of the complete guide, you’ll have a working website with personalization and A/B testing like the images below.
You can customize this project to fit the requirements for your web app.
**Home page for general website visitors**

**Two variants of a running A/B test on a landing page**

**Personalized home page for electric car lovers**

{/*
If you can't wait and want to skip ahead, clone the [repository on GitHub](https://github.com/preprio/laravel-complete-starter) to run the demo locally or visit our [demo website](https://acme-lease.prepr.io/) to see it in action.
These resources and this guide are based on the latest version of Laravel.
*/}
## Prerequisites
Make sure the following is in place before getting started:
- [A free Prepr account](https://signup.prepr.io/?plan=free)
- [An environment with Acme Lease demo data in Prepr](/project-setup/setting-up-environments#create-an-environment)
Now that you know what to expect and have your Prepr environment ready, you can get started with the first step to [set up the Laravel project](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-1-set-up-a-laravel-project).
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide/introduction
---
# Set up a Laravel project
*The instructions below will guide you through the first step of the Laravel complete guide.
These show you how to create a Laravel website with static components.*

## Create a Laravel website with static components
If you have an existing Laravel project then you can skip this section and continue with the next section to [make your Laravel project dynamic with Prepr content](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-2-make-the-project-dynamic).
Otherwise, let's get started.
Great work on getting your static web app working!
Continue your journey to the next section to [make your Laravel project dynamic with Prepr content](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-2-make-the-project-dynamic).
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-1-set-up-a-laravel-project
---
# Make your Laravel project dynamic
*This guide shows you how to connect an existing Laravel project to Prepr to retrieve and show Acme Lease demo content.*
## Connect your Laravel website to Prepr
The steps below continue from the previous section, [Set up a Laravel project](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-1-set-up-a-laravel-project).
If you don't yet have a Laravel website with static pages, follow the steps in this section first.
Otherwise, let's get started.
Congratulations! You have successfully connected your front end to Prepr to make your website dynamic.
Continue your journey to the next section to [set up data collection](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-3-set-up-data-collection).
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-2-make-the-project-dynamic
---
# Set up data collection with tracking
*Prepr is a CMS that includes built-in A/B testing and personalization features.
These capabilities require visitor data to measure your test results and to build segments for personalization.
This chapter of the Laravel complete guide shows you how to enable tracking to collect this data in your Laravel front end.*
## Enable Prepr tracking and collect view events
The steps below continue from the previous section, [Make your Laravel project dynamic](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-2-make-the-project-dynamic).
If you don't yet have a Laravel project connected to Prepr, follow the previous steps listed in the [Complete guide overview](/connecting-a-front-end-framework/laravel/laravel-complete-guide) to create one.
Otherwise, let's get started.
Congratulations! You've successfully enabled Prepr tracking in your Laravel front end, and started collecting page view events.
Now, you're ready to [add A/B testing](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-4-add-ab-testing) and [personalization](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-5-add-personalization) to your website.
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-3-set-up-data-collection
---
# Add A/B testing to your Laravel website
*Prepr CMS enables editors to create A/B tests directly within their content.
This means testing different content versions is simple and effective.
This chapter of the Laravel complete guide demonstrates how to set up A/B testing in Prepr, including testing the display of variants and measuring the results.*
At the end of this section, you'll see the A and B versions on the *Electric Lease Landing Page*.

The steps below make use of the A/B test in the *Electric Lease Landing Page* content item from the Acme Lease demo data.

This chapter continues from the previous section, [Set up data collection](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-3-set-up-data-collection).
If you haven't yet enabled Prepr tracking in your Laravel project, follow the steps in this section first.
Otherwise, let's get started.
## Set up A/B testing for the *Electric Lease Landing Page*
You can run an A/B test on parts of a web page to show two different versions of the content to different visitors and compare which variant is more engaging.
To show the right variant to the right visitor:
- Every visitor gets an ID that resolves to an A or a B variant.
- In your front end, you need to send this ID along with the query to retrieve the right variant.
- This is done by setting the value for the API request header, `Prepr-Customer-Id`, when you make the API request to retrieve the content.
Congratulations! You have a running A/B test with metrics in your Laravel website.
Now, you can [Add personalization](/connecting-a-front-end-framework/nuxtjs/nuxt-complete-guide/step-5-add-personalization) to your website.
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-4-add-ab-testing
---
# Add personalization to your Laravel website
*Prepr CMS enables editors to create adaptive content directly in their content items to personalize elements of web pages.
This makes setting up personalization really simple and effective in increasing engagement and conversions.
This chapter of the Laravel complete guide shows you how to add personalization to your Laravel website.*
At the end of this section, you'll see a personalized home page for visitors interested in electric cars.

The steps below make use of the Adaptive content in the *Homepage* content item from the Acme Lease demo data.

This chapter continues from the previous section, [Set up data collection](/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-3-set-up-data-collection).
If you haven't yet enabled Prepr tracking in your Laravel project, follow the steps in this section first.
Otherwise, let's get started.
## Set up personalization for the home page
You can show different personalized versions of the content to different groups (*Segments*) of visitors.
To show the right variant to the right visitor:
- Every visitor is given an ID that resolves to a segment they belong to.
- In your front end, you need to send this ID along with the query to retrieve the right variant.
- This is done by setting the value for the API request header, `Prepr-Customer-Id`, when you make the API request to retrieve the content.
Congratulations! You have successfully set up personalization with metrics in your Laravel website.
This brings you to the end of the Laravel complete guide which has given you all the tools and tips you need to create your own web app
connected to Prepr CMS complete with personalization, and A/B testing.
Don't hesitate to [give us feedback](mailto:feedback@prepr.io) on your experience using this guide.
## Next steps
To learn more on how to expand your project, check out the following resources:
- [Set up visual editing](/project-setup/setting-up-previews-and-visual-editing)
- [More data collection details](/data-collection)
- [More about A/B testing](/ab-testing)
- [More about personalization](/personalization)
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide/step-5-add-personalization
---
# Complete guide to Laravel and Prepr
*This guide shows you how to connect Prepr to a Laravel project including styling, adaptive content, and A/B testing.*
Follow the steps below in the recommended order to set up your own working Laravel website with adaptive content, A/B testing and a preview bar.
You can also customize this project to fit the requirements for your web app.
Source: https://docs.prepr.io/connecting-a-front-end-framework/laravel/laravel-complete-guide
---
# Adding images to web apps
*This step-by-step guide describes how to add images to your web application.*
## Introduction
Prepr simplifies asset management and provides multi-format support.
This guide covers the end-to-end workflow from defining image assets in your Prepr schema, adding images to content items and fetching them via the API.
## Prerequisites
You need to have the following setup before you can render images in your web application.
- [A free Prepr account](https://signup.prepr.io/)
- [At least one content model](/content-modeling/managing-models#create-a-model) in your environment.
## Adding images to your web application
The steps below make use of the example of a cover image in blog articles.
That's it. Once you [publish your content item](/content-management/managing-content/managing-content-items#publish-a-content-item), images become available in your web application in the best possible format.
## Want to learn more?
Check out the following guides to learn how to add other asset types to your web application:
- [Video & Audio](/development/best-practices/assets/video-audio)
- [Live video stream](/development/best-practices/assets/live-video-stream)
- [Files](/development/best-practices/assets/files)
Source: https://docs.prepr.io/development/best-practices/assets/images
---
# Adding video and audio to web apps
*Follow this guide to learn how to add video and audio files to your web application.*
## Introduction
Prepr simplifies asset management and provides multi-format support so you can embed video and audio files in your web application.
This guide covers the end-to-end workflow from defining video/audio assets in your Prepr schema, adding these assets to content items and fetching them via the API.
## Prerequisites
You need to have the following setup before you can embed video and audio files in your web application.
- [A free Prepr account](https://signup.prepr.io/)
- [At least one content model](/content-modeling/managing-models#create-a-model) in your environment.
## Embedding video and audio files in your web application
The steps below make use of the example of including a video or audio file in the content ([*Dynamic content* field](/content-modeling/field-types#dynamic-content-field)) of blog articles.
## Want to learn more?
Check out the following guides to learn how to add other asset types to your web application:
- [Live video stream](/development/best-practices/assets/live-video-stream)
- [Images](/development/best-practices/assets/images)
- [Files](/development/best-practices/assets/files)
Source: https://docs.prepr.io/development/best-practices/assets/video-audio
---
# Adding live video streams to web apps
*Follow this guide to learn how to add a live video stream to your web application using Prepr.*
## Introduction
Imagine you are about to hold a corporate event.
In this case, you may want to create a web page to announce and broadcast your virtual event.
Also, recording your online event might be essential, especially if you attract attendees located in another time zone or want to send out the replay to all registrants.
Prepr makes all this incredibly simple with the *Live streaming* feature powered by Mux's advanced video services.
Prepr works as a centralized interface you can use for the following purposes:
- Create and manage a live video stream.
- Integrate a live video stream in a web application using the API and a video player.
- Save and store your live stream as an on-demand video recording.
## Prerequisites
You need to have the following setup before you can render images in your web application.
- [A free Prepr account](https://signup.prepr.io/)
- [At least one content model](/content-modeling/managing-models#create-a-model) in your environment.
## Adding live streams to your web application
The steps below make use of the example of a live stream in a blog article.
## Want to learn more?
Check out the following guides to learn how to add other asset types to your web application:
- [Images](/development/best-practices/assets/images)
- [Video & Audio](/development/best-practices/assets/video-audio)
- [Files](/development/best-practices/assets/files)
Source: https://docs.prepr.io/development/best-practices/assets/live-video-stream
---
# Adding files to web apps
*This guide describes adding different file formats such as PDF, ZIP, GIF, and more to your web application using Prepr.*
## Introduction
Prepr allows you to add several file formats to a web application.
You can use popular file types like `PDF` and `ZIP`, for example, to publish a *Press kit*, *Terms of use*, or any other document you want your website visitors to download.
## Prerequisites
You need to have the following setup before you can embed downloadable files in your web application.
- [A free Prepr account](https://signup.prepr.io/)
- [At least one content model](/content-modeling/managing-models#create-a-model) in your environment.
## Adding downloadable files to your web application
The steps below make use of the example of downloadable files in blog articles.
That's it. Once you [publish your content item](/content-management/managing-content/managing-content-items#publish-a-content-item), your file becomes available in your web application for download.
## Want to learn more?
Check out the following guides to learn how to add other asset types to your web application:
- [Images](/development/best-practices/assets/images)
- [Video & Audio](/development/best-practices/assets/video-audio)
- [Live video stream](/development/best-practices/assets/live-video-stream)
Source: https://docs.prepr.io/development/best-practices/assets/files
---
# Working with assets
Follow these guidelines to help you include images, videos, audios, and files in your web applications.
Source: https://docs.prepr.io/development/best-practices/assets