Skip to main content

Command Palette

Search for a command to run...

SharePoint REST API - Working with Site Content Types

Updated
20 min readView as Markdown
SharePoint REST API - Working with Site Content Types
R

I'm a .NET/M365 developer, trainer, author, MVP & MCT Alumni

This post covers the /_api/web/contenttypes endpoint — the site-level REST interface for SP.ContentType in SharePoint Online. It follows on from the earlier posts in this series on lists, list items, and fields. A companion post covers list-scoped content types at /_api/web/lists(guid'...')/contenttypes.


Introduction

SP.ContentType is the REST representation of a SharePoint content type. Site content types are defined at the web level and can be applied to multiple lists. They live at:

https://<tenant>.sharepoint.com/sites/<site>/_api/web/contenttypes

Content type IDs in SharePoint are hierarchical: a child content type's ID is formed by appending a hex suffix to its parent's ID. The built-in System content type (0x) is the root of the full hierarchy. Item (0x01) derives from System and is the base for most familiar SharePoint item-based content types — a direct Item child might have an ID like 0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98. When you retrieve a content type via the REST API, the Id property is an object {"StringValue": "0x..."}. The flat string form is available as StringId, and that string value is what you pass in URL segments.

Things you can do with site content types via the REST API:

  • List all site content types, or filter to a specific group

  • Read a single content type by its StringId

  • Create a new content type — either directly in the current web with a simplified body, or as an explicit child of a parent identified by StringId using the /contenttypes/Create endpoint

  • Update content type properties (name, description, group)

  • Propagate field link customizations to child content types

  • Delete a content type from the site

  • Manage field links — the columns associated with the content type and their form behavior

Authentication note: The raw HTTP examples use a SharePoint Bearer token. With delegated authentication, the token represents a signed-in user and the operation must be permitted by both the delegated scope and that user's SharePoint permissions. With application (app-only) authentication, the token represents the Entra application and SharePoint evaluates its application permissions. For Entra app-only access to SharePoint REST, Microsoft documents certificate-based authentication; client-secret app-only tokens are not accepted by SharePoint REST. The legacy SharePoint ACS app-only model was retired on November 27, 2023 and stopped working on April 2, 2026. In SPFx, use SPHttpClient instead — it supplies the current user's SharePoint authentication context and manages request digests for write operations.


Before You Start: Headers and JSON Format

SharePoint's REST API defaults to OData v3 in most cases when the OData-Version header is not supplied. To use OData v4 — which changes how type annotations are expressed and is what SPFx's SPHttpClient uses by default — include the OData-Version: 4.0 header on your requests.

The Accept header tells the API what JSON format to return. The Content-Type header (on requests with a body) tells the API what JSON format you're sending. All examples in this post use application/json;odata.metadata=none, which returns clean JSON with no OData metadata noise.

OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none  (JSON-body requests only)

For a full explanation of OData versions, JSON format options, and how these headers interact, see Understanding SharePoint REST JSON Formats.


The SP.ContentType Entity

The most important properties returned by the content type endpoint:

Property Type Description
StringId string The content type ID as a flat string (e.g. 0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98). Use this value in URL segments.
Id object The content type ID as an object: {"StringValue": "0x..."}. Contains the same value as StringId.
Name string Display name shown in the content type picker and list settings.
Description string Optional description shown in list settings.
Group string Content type group name (e.g. "Document Content Types", "List Content Types").
Hidden bool Whether the content type is hidden from the content type picker.
ReadOnly bool When true, changes to the content type's properties are denied until the read-only setting is removed.
Sealed bool When true, the content type cannot normally be modified and does not receive standard push-down updates. Site collection administrator rights are required to unseal it.
Scope string Server-relative URL of the web where the content type is defined (e.g. /sites/marketing). For list-scoped copies, reflects the list URL instead.
SchemaXml string The full CAML XML schema of the content type. Although documented as read-only, SharePoint Online accepts it in REST MERGE requests and returns 204. In testing, field additions supplied through the XML are silently dropped — it is not a viable REST mechanism for adding columns to a content type.

API Operations

The examples below are independent — each one can be run on its own against any site with the appropriate content types present.

List All Site Content Types

Returns all content types defined at the web level, including SharePoint built-ins.

GET https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Response — 200 OK (abridged — the collection includes all built-in and custom content types)

{
  "value": [
    {
      "StringId": "0x01",
      "Name": "Item",
      "Group": "_Hidden",
      "Hidden": false,
      "Scope": "/sites/marketing"
    },
    {
      "StringId": "0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98",
      "Name": "Contract",
      "Group": "Contoso Content Types",
      "Hidden": false,
      "Scope": "/sites/marketing"
    }
  ]
}

The collection typically includes dozens of built-in content types. Use $filter to narrow the results:

GET https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes?$filter=Group eq 'Contoso Content Types'&$select=StringId,Name,Description,Group
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Get a Site Content Type

Retrieves a single content type with a key lookup using its StringId.

GET https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Response — 200 OK (abridged)

{
  "StringId": "0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98",
  "Id": {
    "StringValue": "0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98"
  },
  "Name": "Contract",
  "Description": "A contract document",
  "Group": "Contoso Content Types",
  "Hidden": false,
  "ReadOnly": false,
  "Sealed": false,
  "Scope": "/sites/marketing"
}

Quirk: If the content type ID does not exist, SharePoint returns an empty object {} rather than a 404. Check for a missing StringId property in the response to detect this case, or confirm deletion by querying the collection: $filter=StringId eq '0x...' (validated against SharePoint Online).


Create a Site Content Type

Creates a content type at the site level using a direct POST. In SharePoint Online testing, SharePoint uses the hierarchical ID supplied in Id.StringValue to determine the parent relationship, but does not preserve the complete ID you provide — it generates its own unique ID and returns that in the response. To specify the parent explicitly by ID and use a named parameter instead of an inferred prefix, use Create a Child Content Type instead (validated against SharePoint Online).

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
Content-Type: application/json;odata.metadata=none

{
  "@odata.type": "#SP.ContentType",
  "Id": { "StringValue": "0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98" },
  "Name": "Contract",
  "Group": "Contoso Content Types",
  "Description": "A contract document"
}

Response — 201 Created — the full SP.ContentType entity.


Create a Child Content Type

Creates a child content type that inherits from an existing parent. SharePoint derives the new content type's ID automatically by appending a suffix to the parent's ID. Use the /contenttypes/Create endpoint with the body wrapped in a parameters key.

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes/Create
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
Content-Type: application/json;odata.metadata=none

{
  "parameters": {
    "@odata.type": "#SP.ContentTypeEntityData",
    "ParentContentTypeId": "0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98",
    "Name": "Amendment",
    "Group": "Contoso Content Types",
    "Description": "An amendment to a contract"
  }
}

Response — 200 OK — the full SP.ContentType entity. In testing, children created under custom parents received a short generated suffix, while children created directly under short built-in parents could instead receive 00 followed by a 32-character GUID. In either case, the suffix is generated by SharePoint.

Critical: The parameters wrapper key is required. Placing @odata.type or any other property at the top level of the request body returns 400 Bad Request with the error "The parameter does not exist in method Create." Both the @odata.type annotation and all content type properties must be inside parameters (validated against SharePoint Online).

Note: This function returns 200 OK, not 201 Created.


Update a Site Content Type

Updates content type properties using the MERGE tunnel pattern.

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
Content-Type: application/json;odata.metadata=none
X-HTTP-Method: MERGE

{
  "Description": "An executed contract document",
  "Group": "Contoso Legal"
}

Response — 204 No Content

Note: Property updates (Name, Description, Group) via MERGE are not propagated to child content types even if you subsequently call update(updateChildren=true). The updateChildren flag only propagates field link changes — not property changes (validated against SharePoint Online).

Note: Although SchemaXml is documented as read-only, SharePoint Online accepts it in a REST MERGE request and returns 204. In testing, field additions supplied through the XML were discarded — this is not a viable REST mechanism for adding columns to a content type. Use CSOM or PnP instead.


In SharePoint Online testing, this pushes field link customizations (Hidden, Required) from a parent content type down to all of its child content types. Call this after customizing field links via the FieldLinks endpoint.

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')/update(updateChildren=true)
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
Content-Type: application/json;odata.metadata=none

{}

Response — 204 No Content

Note: update(updateChildren=true) propagates field link changes (the Hidden and Required settings on inherited columns) to child content types. It does not propagate property changes such as Name, Description, or Group — those changes apply only to the content type you updated directly (validated against SharePoint Online).

Note: The recommended sequence when customizing field links and propagating: first update any content type properties with MERGE if needed, then customize field links via the FieldLinks endpoint, then call update(updateChildren=true) to push the field link changes to child content types.


Delete a Site Content Type

Uses the DELETE tunnel pattern. Content-Length: 0 is required.

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
X-HTTP-Method: DELETE
Content-Length: 0

Response — 204 No Content

Note: A site content type cannot be deleted while list content types or child site content types are based on it. Remove it from consuming lists and delete its child site content types first.

Note: After deletion, looking up the deleted content type by its ID returns an empty object {} rather than a 404. To confirm deletion, query the collection with $filter=StringId eq '0x...' (validated against SharePoint Online).

Note: Content-Length: 0 is required. SharePoint Online returns 411 Length Required without it. Most HTTP clients add it automatically, but it must be set explicitly in some environments.


Field links are the columns associated with a content type. Each entry in the FieldLinks collection represents one column that is part of the content type's schema. The REST FieldLinks endpoint for a site content type is narrower than its list-content-type counterpart. A field link can only target a field that already exists on the parent content type; REST cannot introduce an arbitrary site column into the content type. In SharePoint Online testing, the endpoint was useful for customizing properties such as Hidden and Required on those inherited fields. For list content types the constraint is different — REST can add a field link to a list content type for any column that already exists on the list.

Property Type Description
Id Guid The unique identifier of the site column this field link points to.
Name string The field's internal name. The most reliable identifier when reading field links.
DisplayName string Returns an empty string on GET in SharePoint Online testing — use Name to identify the field.
FieldInternalName string Returns an empty string on GET in SharePoint Online testing. It is the operative property when targeting a field in a POST body.
Hidden bool Whether the column is hidden in list forms for this content type.
Required bool Whether the column is required for this content type.
ReadOnly `bool null`
ShowInDisplayForm `bool null`

Returns all field links for the content type.

GET https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')/FieldLinks
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Response — 200 OK

{
  "value": [
    {
      "Id": "c042a256-787d-4a6f-8a8a-cf6ab767f12d",
      "Name": "ContentType",
      "DisplayName": "",
      "FieldInternalName": "",
      "Hidden": true,
      "Required": false
    },
    {
      "Id": "fa564e0f-0c70-4ab9-b863-0177e6ddd247",
      "Name": "Title",
      "DisplayName": "",
      "FieldInternalName": "",
      "Hidden": false,
      "Required": true
    },
    {
      "Id": "6e8f95a2-4b3c-4d21-9e1f-2a8c7b4e3f51",
      "Name": "ContractDate",
      "DisplayName": "",
      "FieldInternalName": "",
      "Hidden": false,
      "Required": false
    }
  ]
}

Note: DisplayName and FieldInternalName return empty strings on GET in SharePoint Online testing. Use the Name property — it contains the field's internal name and is the reliable way to identify which column a field link refers to.


Retrieves a single field link by the site column's GUID.

GET https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')/FieldLinks(guid'fa564e0f-0c70-4ab9-b863-0177e6ddd247')
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>

Response — 200 OK

{
  "Id": "fa564e0f-0c70-4ab9-b863-0177e6ddd247",
  "Name": "Title",
  "DisplayName": "",
  "FieldInternalName": "",
  "Hidden": false,
  "Required": true,
  "ReadOnly": null,
  "ShowInDisplayForm": null
}

Note: ReadOnly and ShowInDisplayForm return null from this single field-link lookup (validated against SharePoint Online).


Updates the Hidden or Required setting of an inherited field link. Target the column using FieldInternalName in the request body.

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')/FieldLinks
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
Content-Type: application/json;odata.metadata=none

{
  "@odata.type": "#SP.FieldLink",
  "FieldInternalName": "Title",
  "Hidden": false,
  "Required": true
}

Response — 200 OK (delegated) or 201 Created (app-only) — the resulting SP.FieldLink entity. The status code differs between delegated and application authentication (validated against SharePoint Online).

Restrictions (validated against SharePoint Online):

  • A field link can only target a field that already exists on the parent content type's effective field links. REST cannot introduce an arbitrary site column — if FieldInternalName names a column not already present, SharePoint returns 400 Bad Request with "Column does not exist." Adding a site column to a site content type requires CSOM or PnP.

  • FieldInternalName is the operative property for identifying the target column — Id and Name are not required in the body and do not affect the lookup.


Sets the display order of columns in the content type. Pass all field internal names in the desired order.

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')/FieldLinks/Reorder
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
Content-Type: application/json;odata.metadata=none

{
  "internalNames": ["ContractDate", "Title", "ContentType"]
}

Response — 204 No Content

Note: Reorder returns 204 No Content, but a subsequent GET on the FieldLinks collection returns the same order as before the call — the new order is not reflected in the REST response (validated against SharePoint Online with both delegated AllSites.Manage and app-only Sites.Manage.All). This confirms only that the requested order is not reflected by the REST FieldLinks collection; it does not establish that SharePoint ignored the reorder for all other representations or in the UI.


The REST endpoint accepts the DELETE tunnel pattern for a field link on a site content type, but in SharePoint Online testing deletion was silently ignored for the built-in inherited field links that were testable.

POST https://contoso.sharepoint.com/sites/marketing/_api/web/contenttypes('0x0100CDBF9DC95E6E4F46B2B3E56D5C0E4D98')/FieldLinks(guid'fa564e0f-0c70-4ab9-b863-0177e6ddd247')
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
X-HTTP-Method: DELETE
Content-Length: 0

Response — 204 No Content

Note: Deletion is silently ignored for the built-in inherited field links ContentType and Title — the response is 204 but those field links remain (validated against SharePoint Online). Because REST cannot add a field link for a site column that is not already present on the parent content type, the DELETE behavior for such a custom field link added via CSOM or PnP is not established here. For list-scoped content types, the same silent-ignore behavior was observed for both built-in and custom field links added via REST.


Quick Reference

Headers

Header Notes
Authorization: Bearer <token> Required for the raw HTTP OAuth examples in this post. When using SPHttpClient in SPFx, authentication is handled automatically.
Accept: application/json;odata.metadata=none Required if you want a JSON response. Omitting it causes SharePoint to return Atom XML.
Content-Type: application/json;odata.metadata=none Required on JSON-body requests.
OData-Version: 4.0 Activates OData v4 behavior.
X-HTTP-Method: MERGE Tunnels an update over POST.
X-HTTP-Method: DELETE Tunnels a delete over POST.
If-Match: * Not required for content type or field link MERGE and DELETE operations — SharePoint Online accepts those requests without it (validated against SharePoint Online). SharePoint's documented ETag concurrency model applies to lists and list items, not content types.
Content-Length: 0 Required on bodyless DELETE tunnel requests — SharePoint Online returns 411 Length Required without it (validated). Most HTTP clients add it automatically.

Response Status Codes

Operation Status Body
GET content types or single content type 200 OK JSON
GET nonexistent content type ID 200 OK {}
Create content type (POST /contenttypes) 201 Created Full SP.ContentType entity
Create child content type (POST /contenttypes/Create) 200 OK Full SP.ContentType entity
Customize field link (POST /FieldLinks) — delegated 200 OK SP.FieldLink entity
Customize field link (POST /FieldLinks) — app-only 201 Created SP.FieldLink entity
Update content type (MERGE) 204 No Content Empty
Propagate field link changes (POST /update) 204 No Content Empty
Reorder field links (POST /FieldLinks/Reorder) 204 No Content Empty
Delete a site content type (DELETE tunnel) 204 No Content Empty
Attempt to delete inherited field link (DELETE tunnel, silently ignored) 204 No Content Empty; field link remains

Permission Requirements

The table below lists the minimum delegated and application permission scopes. Delegated scopes apply when calling with a Bearer token on behalf of a signed-in user; the minimum user access column shows what access the signed-in user needs when using classic SharePoint permissions and modern SharePoint group permissions. Application permissions apply when calling without a signed-in user context (app-only). The application permissions in this table are permissions on the Office 365 SharePoint Online API, not the same-named Microsoft Graph permissions. An SPFx solution using SPHttpClient does not require you to grant these delegated or application scopes to the solution; it calls SharePoint as the current user, whose SharePoint permissions govern the operation. Delegated permission scope requirements were validated against SharePoint Online using a user with site Member access. The classic permission requirements in the table come from Microsoft's documentation, except where live validation is explicitly noted; for content type writes, classic Edit was also validated.

Operation Minimum delegated scope Minimum user access Minimum tenant-wide application permission
List all site content types AllSites.Read Classic: Read; Group: Visitor Sites.Read.All
Get a site content type AllSites.Read Classic: Read; Group: Visitor Sites.Read.All
List field links AllSites.Read Classic: Read; Group: Visitor Sites.Read.All
Get a field link AllSites.Read Classic: Read; Group: Visitor Sites.Read.All
Create a content type AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All
Create a child content type AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All
Update a content type (MERGE) AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All
Propagate field link changes AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All
Delete a content type AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All
Customize a field link AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All
Reorder field links AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All
Attempt to delete a field link AllSites.Manage Classic: Edit; Group: Member Sites.Manage.All

Note: Sites.ReadWrite.All is not sufficient for any content type write operation — all writes require Sites.Manage.All (validated against SharePoint Online).

Note: The application permissions above are tenant-wide grants. For least-privilege app-only access to specific site collections, the SharePoint Sites.Selected application permission can be used instead. Sites.Selected consent alone does not grant access to any site — an explicit Read, Write, Manage, or FullControl role must be assigned on each target site collection. For the write operations in this post, assign the selected site a Manage or FullControl role rather than Write.


Wrapping Up

Site content types are one of SharePoint's deeper schema features — and the REST API surface for them has several non-obvious behaviors worth knowing before you hit them in production. A few things to keep in mind:

  • Content type IDs are hierarchical. A child has the parent's ID as a prefix followed by a suffix. Both creation paths produce children of the specified parent. The practical difference is how the parent is specified and how the ID is assigned: direct POST infers the parent from the Id.StringValue prefix but does not preserve the suffix you supply; /contenttypes/Create takes the parent explicitly via ParentContentTypeId and derives the full ID automatically. Neither path lets you control the exact ID string (validated against SharePoint Online).

  • The /contenttypes/Create body must wrap all properties inside a "parameters" key. Bare properties at the top level return 400 Bad Request with a message identifying the offending property name (validated).

  • /contenttypes/Create returns 200 OK, not 201 Created.

  • After deleting a content type, getById returns {} instead of a 404. Confirm deletion by filtering the full collection.

  • updateChildren propagates field link changes (Hidden, Required) to child content types — but not property changes like Name or Description. Call it after field link customizations, not after a properties MERGE.

  • The REST FieldLinks endpoint for a site content type can only target fields that already exist on the parent content type — it cannot introduce an arbitrary site column. In SharePoint Online testing, it was useful for customizing properties such as Hidden and Required on those inherited fields. For list content types, REST can add a field link for any column that already exists on the list.

  • DisplayName and FieldInternalName always read back as empty strings from the FieldLinks collection. Use Name to identify which column a field link refers to.

  • All content type write operations require Sites.Manage.All for app-only access — Sites.ReadWrite.All is not sufficient (validated).

Happy coding!