Skip to content

Custom Types

Custom Types let you define your own content types beyond the built-in types (page, article, recipe, homepage, landing, and more) provided by EngageHQ.

Overview

Every content item in EngageHQ has a content_type that determines its available fields, allowed sections, and editor layout. The built-in types cover common use cases, but organizations often need specialized content structures — event pages, product showcases, location guides, etc.

Custom Types allow you to:

  • Define custom fields — Add structured data fields (text, number, boolean, select, etc.) specific to the content type
  • Control section availability — Restrict which built-in content sections and platform elements are available in the editor
  • Configure editor layout — Choose which sidebar cards appear (SEO, tags, publishing, etc.)

Admin UI

Custom types can also be created and managed through the Retail Success Platform admin dashboard under Content Studio > Custom Types, without using the API directly.


Required Permissions

All Custom Type management endpoints require a valid JWT with the appropriate engagehq:custom_types.* scope:

EndpointRequired Scope
GET list / active / showengagehq:custom_types.view
POST createengagehq:custom_types.create
PUT updateengagehq:custom_types.update
DELETE deleteengagehq:custom_types.delete

Requests missing the required scope will receive a 403 Forbidden response.


Create Custom Type

Required scope: engagehq:custom_types.create

http
POST /api/v1/content/custom-types

Request Body

json
{
  "type_key": "event_page",
  "display_name": "Event Page",
  "description": "Pages for promotional events and in-store activities",
  "schema": {
    "fields": [
      {
        "key": "event_date",
        "label": "Event Date",
        "type": "datetime",
        "required": true
      },
      {
        "key": "event_location",
        "label": "Location",
        "type": "text",
        "required": false
      },
      {
        "key": "registration_url",
        "label": "Registration URL",
        "type": "url",
        "required": false
      }
    ],
    "allowed_slice_types": [
      "hero", "rich_text", "image", "gallery", "cta", "form"
    ],
    "sidebar_cards": [
      "publishing", "seo", "tags", "featured_image"
    ]
  }
}

Request Fields

FieldTypeRequiredDescription
type_keystringYesUnique identifier for the type. Lowercase letters, numbers, and underscores only (snake_case) — no hyphens. Cannot be changed after creation.
display_namestringYesHuman-readable name shown in the admin UI
descriptionstringNoDescription of what this content type is for
schemaobjectYesType definition containing fields, allowed sections, and layout config
schema.fieldsarrayYesArray of custom field definitions (see Field Types)
schema.allowed_slice_typesarrayYesWhich built-in section types are available in the editor
schema.sidebar_cardsarrayYesWhich sidebar cards appear in the editor
ui_schemaobjectNoAdditional UI configuration hints
validation_rulesobjectNoCustom validation rules for the type's fields
default_valuesobjectNoDefault values for custom fields when creating new content

Response

json
{
  "success": true,
  "data": {
    "id": "cty_00000k1L2m3N4o5",
    "type_key": "event_page",
    "display_name": "Event Page",
    "description": "Pages for promotional events and in-store activities",
    "schema": {
      "fields": [
        {
          "key": "event_date",
          "label": "Event Date",
          "type": "datetime",
          "required": true
        },
        {
          "key": "event_location",
          "label": "Location",
          "type": "text",
          "required": false
        },
        {
          "key": "registration_url",
          "label": "Registration URL",
          "type": "url",
          "required": false
        }
      ],
      "allowed_slice_types": [
        "hero", "rich_text", "image", "gallery", "cta", "form"
      ],
      "sidebar_cards": [
        "publishing", "seo", "tags", "featured_image"
      ]
    },
    "is_active": true,
    "created_at": "2026-03-01T12:00:00+00:00",
    "updated_at": "2026-03-01T12:00:00+00:00"
  }
}

Unique Constraint

The type_key must be unique within your organization. Attempting to create a duplicate returns a 422 validation error.


Management API

Endpoints

MethodEndpointDescription
GET/api/v1/content/custom-typesList all custom types
GET/api/v1/content/custom-types/activeList active types (lightweight)
POST/api/v1/content/custom-typesCreate a new custom type
GET/api/v1/content/custom-types/{id}Get a single custom type
PUT/api/v1/content/custom-types/{id}Update a custom type
DELETE/api/v1/content/custom-types/{id}Delete a custom type

List Custom Types

Required scope: engagehq:custom_types.view

http
GET /api/v1/content/custom-types

Query Parameters

ParameterTypeDefaultDescription
searchstring-Search by display name, type key, or description
is_activeboolean-Filter by active status
per_pageinteger20Items per page (1-100)
pageinteger1Page number

Example Request

javascript
const response = await fetch(
  'https://cdn.engagehq.retailsuccessplatform.com/api/v1/content/custom-types',
  {
    headers: {
      'Authorization': `Bearer ${token}`,
      'X-Organization-Context': organizationId,
    },
  }
);

List Active Types (Lightweight)

Required scope: engagehq:custom_types.view

Returns only active custom types with minimal fields (type_key and display_name). Useful for populating navigation or content type selectors.

http
GET /api/v1/content/custom-types/active

Show Custom Type

Required scope: engagehq:custom_types.view

http
GET /api/v1/content/custom-types/{id}

Path Parameters

ParameterTypeDescription
idstringCustom type hashkey (cty_xxxxx)

Update Custom Type

Required scope: engagehq:custom_types.update

http
PUT /api/v1/content/custom-types/{id}

Accepts the same fields as the create endpoint except type_key, which is immutable. Only included fields are updated.

Example Request

javascript
const response = await fetch(
  `https://cdn.engagehq.retailsuccessplatform.com/api/v1/content/custom-types/${typeId}`,
  {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      'X-Organization-Context': organizationId,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      display_name: 'Community Event',
      schema: {
        fields: [
          { key: 'event_date', label: 'Event Date', type: 'date', required: true },
          { key: 'event_location', label: 'Location', type: 'text', required: false },
          { key: 'rsvp_limit', label: 'RSVP Limit', type: 'number', required: false },
        ],
        allowed_slice_types: ['hero', 'rich_text', 'image', 'gallery', 'cta', 'form'],
        sidebar_cards: ['publishing', 'seo', 'tags', 'featured_image'],
      },
    }),
  }
);

Delete Custom Type

Required scope: engagehq:custom_types.delete

http
DELETE /api/v1/content/custom-types/{id}

Deletion Protection

A custom type cannot be deleted if any content items use it. The API returns a 422 error indicating how many content items reference the type. You must delete or reassign all content items before deleting the type.


Schema Definition

The schema object defines the structure of a custom type.

Fields

Each field in schema.fields defines a custom data field that content editors fill in when creating content of this type.

PropertyTypeRequiredDescription
keystringYesUnique field identifier (used in content JSON)
labelstringYesDisplay label in the editor
typestringYesField type (see below)
requiredbooleanNoWhether the field is required (default: false)
placeholderstringNoPlaceholder text for the input
help_textstringNoHelp text shown below the input
defaultmixedNoDefault value
optionsarrayNoOptions for select fields — each entry is a string or a { "value", "label" } object (see Select Options)
fieldsarrayNoSub-field definitions for repeater fields (scalar types only)

Supported Field Types

TypeDescriptionRenders As
textSingle-line textText input
rich_textMulti-line textTextarea
numberNumeric valueNumber input
datetimeDate and timeDatetime picker
imageImage assetMedia Library picker
selectSingle selection from optionsDropdown
relationReference to another content itemContent picker
booleanTrue/false toggleCheckbox
urlURL stringURL input
emailEmail addressEmail input
colorHex color valueColor picker
jsonArbitrary JSON valueJSON textarea
repeaterRepeatable group of sub-fieldsAdd/remove/reorder item list

Select Options

A select field's options array accepts either form, and you may mix both in one list:

  • Bare string — used as both the stored value and the display label:

    json
    "options": ["small", "medium", "large"]
  • { value, label } object — when the stored value differs from the label authors see:

    json
    "options": [
      { "value": "mobile", "label": "Mobile menu (drawer)" },
      { "value": "desktop_primary_bar", "label": "Main navigation bar" }
    ]

value is required (non-empty, ≤255 chars) and is stored as the field's value; label is optional (a blank label, or one equal to the value, is stored as a bare string). Repeater sub-fields of type select use the same option shapes.

Repeater Fields

A repeater field defines a fields array of scalar sub-field definitions (same shape as top-level fields, but type must be a scalar type — repeaters cannot be nested). Authors add, remove, and reorder rows in the editor; each row is an object keyed by the sub-field key.

json
{
  "key": "ingredients",
  "label": "Ingredients",
  "type": "repeater",
  "fields": [
    { "key": "name", "label": "Name", "type": "text", "required": true },
    { "key": "quantity", "label": "Quantity", "type": "number" }
  ]
}

The stored value for a repeater field is an array of objects, each keyed by its sub-field key:

json
"custom_fields": {
  "ingredients": [
    { "name": "Flour", "quantity": 2 },
    { "name": "Sugar", "quantity": 1 }
  ]
}

Allowed Slice Types

The schema.allowed_slice_types array controls which built-in content sections are available in the editor for this type. Valid values:

hero, rich_text, image, video, gallery, features, testimonials, stats, cta, form, team, content_block, html

The schema.sidebar_cards array controls which sidebar panels appear in the content editor. Valid values:

publishing, seo, tags, distribution, notes, featured_image, cache_freshness, related_content


Content Delivery

When content created with a custom type is fetched via the Content API, the item's type field is set to the custom type's type_key. Custom field values are returned under metadata.custom_fields, keyed by the field keys you defined.

Filter the list endpoint by passing the type_key to the type parameter. Because custom field values live in metadata, request them with include=metadata on the list endpoint (the single-item /{slug} endpoint always includes metadata):

http
GET /api/v1/public/content?type=event_page&include=metadata
json
{
  "id": "con_00000a1B2c3D4e5",
  "type": "event_page",
  "slug": "summer-bbq-bash",
  "title": "Summer BBQ Bash",
  "metadata": {
    "custom_fields": {
      "event_date": "2026-07-04",
      "event_location": "Store #42 Parking Lot",
      "registration_url": "https://example.com/rsvp"
    }
  }
}

Response field names

The identifier is returned as id (not content_id) and the type as type (not content_type). Custom field values are nested under metadata.custom_fields, not at the top level, and the list endpoint only returns metadata when you pass include=metadata.

Consuming platforms should handle unknown type values gracefully by falling back to a generic page renderer.


Changelog
DateChange
2026-06-03Corrected type_key rule (underscores, not hyphens) and used datetime (not date) in examples; documented {value,label} select options; corrected the Content Delivery response shape (id/type, values under metadata.custom_fields via include=metadata).
2026-06-01Documented the repeater field type.
2026-03-24Added required permissions documentation.
2026-03-24Initial publication.

EngageHQ Public Content Delivery API