# Tutorial: Create Custom Content on Your Site

Source: https://dev.arcxp.com/custom-content/tutorials/tutorial-create-custom-content-on-your-site/

This tutorial walks you through building a structured content type in Arc XP from scratch. You’ll design an events schema, add a record in Data Manager, wire the records into a PageBuilder block, and view the result on a live page.

By the end, you’ll have a working events listing, and you’ll understand the design decisions that matter before you put real data in.

## What you’ll build

![Screenshot 2026-06-30 at 09.25.13 copy.jpg](https://dev.arcxp.com/images/custom-content/tutorial/event-list-page.jpg)![Screenshot 2026-06-30 at 09.25.37.jpg](https://dev.arcxp.com/images/custom-content/tutorial/event-detail-page.jpg)

An event listing page powered by structured records. The schema defines the shape of an event. Data Manager is where editors create and manage individual events. PageBuilder is how those records get to the page.

You’ll work through all three layers in order.

## Before you start

You’ll need:

- A valid Arc XP API token with write access to the Draft API. You’ll use this to create the schema in Step 2.
- Access to Arc XP Data Manager: editors use this for record creation after the schema is created. Managing fields requires Admin access.
- A PageBuilder bundle running locally with the Arc Themes starter. Follow the [how to run PageBuilder locally](https://docs.arcxp.com) guide first if you haven’t done this.
- Arc Themes 4.2.0 or above. The content source block used in Step 5 ships with Themes and isn’t available in earlier versions.
- A `.env` file in your bundle with:

```
CONTENT_BASE=https://api.sandbox.{org}.arcpublishing.com
ARC_ACCESS_TOKEN=<your-access-token>
```

## Step 1: Plan your schema before you build it

This step matters more than all the others. It’s the “design” of your custom content that will impact implementation.

Two things about a schema are difficult or impossible to change once it’s created:

**Field types are permanent.** A field defined as `short-text` cannot be changed to `date-time`. You also cannot remove a field. The only way around this is to create a new content type — not practical once you have live data.

**Search indexing decisions are permanent once set.** Each field has two independent search settings:

- **Retrieval** (`search_settings.retrieval`): controls whether the field is queryable through the Content API. Set this for fields developers need to filter programmatically.
- **Editorial** (`search_settings.editorial`): makes the field searchable inside Data Manager. Used by editors, not the API.
![Screenshot 2026-06-29 at 10.44.44.jpg](https://dev.arcxp.com/images/custom-content/tutorial/search-indexes-toggle.jpg) 

You configure these independently. A field can have retrieval, editorial search, both, or neither.

> **Breaking changes are rejected by the API:**
> 
> The API returns a `400` error if you try to remove a field, change a field’s type, make an optional field required, or remove search indexing from a field.
> 
> Additive changes are always allowed: adding new fields, updating display names and descriptions, or adding search indexing to a field that previously had none.
> 
> Plan your schema structure before loading real data. Changes that seem minor (i.e., switching a text field to a date, or removing a column you added by mistake) are not possible without a full rebuild.

Before moving to Step 2, fill in a table like this for every field you plan to add:

| Field       | Type       | Required? | Retrieval | Editorial |
| ----------- | ---------- | --------- | --------- | --------- |
| title       | short-text | Yes       | keyword   | keyword   |
| event\_date | date-time  | Yes       | No        | No        |
| location    | short-text | No        | keyword   | keyword   |
| description | long-text  | No        | No        | No        |
| organizer   | short-text | No        | No        | No        |
| ticket\_url | short-text | No        | No        | No        |

This is the schema you’ll build in Step 2\. Retrieval is enabled on `title` and `location` so developers can filter events by them. Editorial search is on those same two fields so editors can find records in Data Manager by typing a title or city. `event_date`, `description`, `organizer`, and `ticket_url` are stored and displayed but not indexed. They won’t be filterable via the Content API.

If you know you’ll need to filter records by date (for example, “show only upcoming events”), add retrieval `range` to `event_date` now. You can add indexing to a field later, but it’s cleaner to set it correctly upfront.

Here’s the schema JSON you’ll use in Step 2:

```json
{
  "name": "events",
  "display_name": "Events",
  "description": "Schema for event listings — concerts, conferences, and local happenings.",
  "fields": [
    {
      "key": "title",
      "display_name": "Event Title",
      "type": "short-text",
      "is_required": true,
      "search_settings": { "editorial": ["keyword"], "retrieval": ["keyword"] }
    },
    {
      "key": "event_date",
      "display_name": "Event Date",
      "type": "date-time",
      "is_required": true
    },
    {
      "key": "location",
      "display_name": "Location",
      "type": "short-text",
      "is_required": false,
      "search_settings": { "editorial": ["keyword"], "retrieval": ["keyword"] }
    },
    {
      "key": "description",
      "display_name": "Description",
      "type": "long-text",
      "is_required": false
    },
    {
      "key": "organizer",
      "display_name": "Organizer",
      "type": "short-text",
      "is_required": false
    },
    {
      "key": "ticket_url",
      "display_name": "Ticket URL",
      "type": "short-text",
      "is_required": false
    }
  ]
}
```

## Step 2: Create the schema via the Draft API

You can also create the schema in Data Manager’s **Schema Manager** (Admin access required). To use the Draft API, send a `POST` request to `/draft/v1/schema-type` with the schema JSON from Step 1:

Terminal window

```bash
curl -X POST "https://api.{YOUR_ORG}.arcpublishing.com/draft/v1/schema-type" \
  -H "Authorization: Bearer {YOUR_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{the schema JSON from Step 1}'
```

Replace `{YOUR_ORG}` with your organization identifier and `{YOUR_API_TOKEN}` with a valid Draft API token.

A successful request returns `200 OK` with the schema object, including a `schema_version` of `1` and system-assigned timestamps. A `400` error means the definition is invalid or a schema with that `name` already exists in your org.

Once created, the schema appears in Data Manager’s **Content Types** sidebar. Editors can create records immediately.

![Screenshot 2026-06-29 at 10.17.41.jpg](https://dev.arcxp.com/images/custom-content/tutorial/data-manager-empty-schema.jpg) 

## \[Optional\] Step 3: Add and manage fields in Data Manager

The schema you created via API already has all six fields from Step 1\. If you need to add more fields later, you can do that in Data Manager without touching the API.

In Data Manager, open **Schema Manager** and select **Events** in the **Content Types** sidebar to see the schema and its fields. From here you can add new fields or edit existing ones.

![Screenshot 2026-06-29 at 10.18.26.jpg](https://dev.arcxp.com/images/custom-content/tutorial/schema-manager-fields.jpg) 

> **What you can and cannot change in an existing field:**
> 
> Once a field is saved, its **key** and **type** are locked, an index can be added but not removed, and Required can be switched off but not on. Display name and help text can change at any time, and new fields can always be added. The full rules, and what to do when a saved field needs one of the locked changes, are in [How to manage content types and records in Data Manager](https://dev.arcxp.com/custom-content/how-to-guides/how-to-manage-content-types-and-records-in-data-manager#what-you-can-change-later).
> 
> This is why Step 1 matters. Plan field keys and indexing before you create the schema.

The **Editorial** and **Retrieval** toggles in the field settings edit the same `search_settings` you set in the API request.

## Step 4: Create your first event record

With the schema saved, editors can start adding records.

1. In Data Manager, select **Events** in the **Content Types** sidebar.
2. Click **Create Item**.
3. Fill in the fields:  
  - **Event Title** — `Summer Block Party`
  - **Event Date** — `2026-07-04T18:00:00` (or use the date picker)
  - **Location** — `City Park, Springfield`
  - **Description** — `Annual block party with live music, food trucks, and fireworks.`
4. Click **Save**.
5. Click **Publish**, which appears once the record is saved.
![Screenshot 2026-06-29 at 10.19.10.jpg](https://dev.arcxp.com/images/custom-content/tutorial/create-event-record.jpg) 

The record is stored in Arc XP and queryable via the Content API. Add more records the same way. Each one is an independent entry in the events schema. Editors can create, update, and unpublish records without touching code.

## Step 5: Wire the records into a PageBuilder block

This step is for developers. Arc Themes ships a content source block for fetching a list of structured content records, so you don’t need to write your own content source. You still need to build the front-end component that renders them. Arc Themes doesn’t ship a display block for structured content.

### Add the content source block

Arc Themes blocks aren’t npm dependencies, so there’s nothing to add to `package.json`. Add the block to the `blocks` array in your bundle’s `blocks.json`, and make sure `themesReleaseVersion` is 4.2.0 or above:

```js
// blocks.json
{
  "themesReleaseVersion": "4.2.0",
  "blocks": [
    "@wpmedia/schema-feed-content-source-block"
  ]
}
```

Run `npm install`, then restart `npx fusion start`. The block registers a content source named `schema-feed`. That’s the name you’ll select in PageBuilder.

**Content source parameters:**

| Parameter   | Required | Description                                                                                                                             |
| ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Schema Name | Yes      | Exact schema name from Data Manager (e.g., events)                                                                                      |
| Sort By     | No       | Field to sort by. Sort order is always descending. There’s no ascending option.                                                         |
| Query       | No       | An ArcSL filter expression against schema\_content fields (e.g., schema\_content.location:"City Park"). Not a free-text keyword search. |
| Size        | No       | Records per page, default 25                                                                                                            |
| Page        | No       | Page number, default 1                                                                                                                  |

The endpoint behind this block returns an array of record objects. Field values live under `item.schema_content`.

Want to build your own content source or feature block instead? See [How to Embed Structured Content Records in PageBuilder](https://dev.arcxp.com/custom-content/how-to-guides/how-to-embed-structured-content-records-in-pagebuilder/) for the how-to.

### Build the feature block

Create `components/features/event-list/default.jsx`:

```js
import React from "react";
import PropTypes from "@arc-fusion/prop-types";
import { useContent } from "fusion:content";
import { useFusionContext } from "fusion:context";

const EventList = ({ customFields }) => {
  const { contentConfig, displayAmount } = customFields;
  const { arcSite } = useFusionContext();

  const content = useContent(
    contentConfig
      ? {
          source: contentConfig.contentService,
          query: {
            ...contentConfig.contentConfigValues,
            schemaName: "events",
          },
        }
      : {},
  );

  if (!contentConfig || !content?.data) return null;

  const records = content.data.slice(0, displayAmount || content.data.length);

  return (
    <ul className="event-list">
      {records.map((item) => {
        const { title, event_date, location, description } =
          item.schema_content || {};
        return (
          <li key={item.document_id}>
            {title && <h2>{title}</h2>}
            {event_date && <p>{new Date(event_date).toLocaleDateString()}</p>}
            {location && <p>{location}</p>}
            {description && <p>{description}</p>}
          </li>
        );
      })}
    </ul>
  );
};

EventList.propTypes = {
  customFields: PropTypes.shape({
    contentConfig: PropTypes.contentConfig().tag({
      group: "Configure Content",
      label: "Content Source",
    }),
    displayAmount: PropTypes.number.tag({
      label: "Number of events to display",
      defaultValue: 10,
      group: "Configure Content",
    }),
  }),
};

EventList.label = "Event List";
export default EventList;
```

## Step 6: Configure the block in PageBuilder and publish

1. For local development experience

  1. Start the local Fusion engine:  
```
npx fusion start
```

  1. Go to `http://localhost/pagebuilder/pages`.
2. Or, in Arc XP environment, deploy your bundle then Go to PageBuilder
3. Open or create a page template.
4. Drag the **Event List** block from the block picker into the layout.
5. In the block’s settings panel, open **Configure Content** → **Content Source**.
6. Select `schema-feed` from the dropdown.
7. Set the content source parameters:

  - **Schema Name** — `events` (must match exactly — this value is case-sensitive)
  - **Size** — `10`
8. Save the template and preview the page.  
Your Summer Block Party record should appear. If it doesn’t, check that the schema name matches what you entered in Data Manager and that `ARC_ACCESS_TOKEN` is valid.
9. When the page looks right, click **Publish** in PageBuilder.
10. Open the live URL for that page. The event records are live.
![Screenshot 2026-06-30 at 09.25.13 copy.jpg](https://dev.arcxp.com/images/custom-content/tutorial/event-list-page.jpg) 

You can also implement event detail page with a separate content source, and setting up it as resolver configuration, same way you do for your story/video/gallery detail pages.

![Screenshot 2026-06-30 at 09.25.37.jpg](https://dev.arcxp.com/images/custom-content/tutorial/event-detail-page.jpg) 

## What’s next

You’ve gone through the full cycle: schema design, field configuration, record creation, PageBuilder wiring, and publishing. The events schema is a simple example, but the same pattern works for recipes, people directories, places, products, or any other content type with repeating structured records.

A few things to do before you go further:

- **Add more records**: each additional record you create in Data Manager is immediately publishable and queryable. Start building out the real event list.
- **Load records via the Draft API**: for bulk ingestion or automated workflows, follow same content ingestion workflows at Arc XP (via Draft API or Migration Center).
- **Query records from a headless implementation**: see [Query Structured Records via Content API](https://dev.arcxp.com/custom-content/how-to-guides/how-to-query-search-structured-content-records-via-the-content-api/) how to guide.
- **Read the schema update limitations** in full before you load production data; the [Developer Guide](https://dev.arcxp.com/custom-content/core-concepts/developer-guide/) covers what’s changeable and what isn’t, and why.
