Integrating Content Publisher with any web framework
Content Publisher provides integration for WordPress, Drupal and Next.js websites but it is also possible to use Content Publisher with other website technology. Pantheon won’t provide the turnkey solution but can guide a development team for the integration.
If the target system is using React and Javascript, Pantheon provides an SDK that greatly facilitates the integration.
Configure the SDK at /api/pantheoncloud to receive requests from the Content Publisher system. Real-time preview starts with a request to /api/pantheoncloud/document/{documentId} which is processed by the SDK and redirected to the actual page where it should render. On the rendering page, the SDK uses a short-lived JWT token to receive real-time updates with GQL; re-rendering the article content as changes are made.
NextJS retrieves documents from Content Publisher’s graphql backend on an as-needed basis (or published documents are retrieved during build time in the case of static site generation).
Published documents are indexed by default, and indexed per collection. Searches can be made either with the SDK, or directly to the GraphQL endpoint.
Collection Managers and Admins can configure a content structure for a collection, which is a hierarchy of articles. UI described at https://docs.content.pantheon.io/content-tree
It can be retrieved through GQL with a query such as
query GetSiteContentStructure($id: String!) {
site(id: $id) {
id
contentStructure
}
}
Or as a property in the Site object. Typed as following (contentStructure is a SerializedContentStructure object).
export type ArticleTreeItem = {
type: 'article';
name: string;
published: boolean;
slug: string | undefined;
};
export type CategoryTreeItem = {
type: 'category';
name: string;
};
export type TreeItem = {
id: string;
children?: TreeItem[];
isHidden?: boolean;
} & (ArticleTreeItem | CategoryTreeItem);
export type SerializedContentStructure = {
active: TreeItem[];
uncategorized: TreeItem[];
};
These are reusable React components which content editors can insert directly into Google Docs. The schema and rendering of these components is handled by the developer, while the attributes of each component is configured by the content editor.
Searches can be done with the SDK, for example:
getAllArticlesWithSummary is typed as follows
async function getAllArticlesWithSummary(
args?: Parameters<typeof getArticles>[1],
options?: Parameters<typeof getArticles>[2],
withSummary?: boolean,
)
The summary is an LLM answer to your search query, using the search results as context. An example from the starter kit is:
await PCCConvenienceFunctions.getAllArticlesWithSummary(
{
publishingLevel: "PRODUCTION",
},
searchParams.q
? {
bodyContains: searchParams.q,
}
: undefined,
true,
);
Or directly to GQL. Here is an example of searching across multiple collections (more in docs)
query {
articlesv3 (
siteIds:["loAWY0YB0HTHexSzw3Z1", "lqAWY0YC0HTHexSzw1Z" ],
filter: {
body: {
contains: "your search query"
}
}) {
articles {
id
title
siteId
}
}
}
If the target system can’t use the Content Publisher SDK, content can still be delivered and integrated relying on Content Publisher GraphQL delivery API and our Webhook support. This is the way our WordPress and Drupal integration work.
When real-time preview starts, Content Publisher will send a request to /api/pantheoncloud/document/{documentId} with query params:
- pccGrant this is a JWT needed for real-time access
- publishingLevel usually this will be REALTIME
- versionId specific id of the version (you may not always receive this
For real-time updates, subscribe to GQL at this endpoint:
wss://gql.content.pantheon.io/sites/{siteId}/query
With a connection param using the realtime JWT provided (we use connectionParams from the Apollo client):
{
"PCC-TOKEN": pccGrant
}
Exact implementation will depend on your language & library used, but the query shape is:
subscription OnArticleUpdate(
$id: String!
$contentType: ContentType
$publishingLevel: PublishingLevel
$versionId: String
) {
article: articleUpdate(
id: $id
contentType: $contentType
publishingLevel: $publishingLevel
versionId: $versionId
) {
# Returns full article fields including content
}
}
article.publish and article.unpublish events will be sent to the collection’s registered webhook. This can be used to update your own index/mirror of documents. Use the articleId and siteId to retrieve the latest version of that article from GQL.
{
event: "article.publish"|"article.unpublish",
payload: {
articleId: XYZ,
siteId: ABC,
}
Refer to the description of Content Structure in the next.js section above.
- Signal to Content Publisher that you support smart components by responding to /api/pantheoncloud/component_schema with the full schema (of type SmartComponentMap, which is basically a dictionary of each component type.
type SmartComponentMap = Record<string, {
title: string; // Display name
variants?: string[]; // Optional variants (e.g., ["standard", "hero"])
iconUrl?: string | null; // Icon for the add-on UI
exampleImageUrl?: string | string[] | null; // Preview images
fields: Record<string, FieldSchema>;
}>;
type FieldSchema = {
type: "string" | "textarea" | "number" | "boolean" | "date" | "file" | "enum" | "object";
displayName: string;
required: boolean;
defaultValue?: string | number | boolean | Array<string | number | boolean>;
multiple?: boolean;
// For enum type:
options?: Array<string | { label: string; value: string }>;
// For object type:
fields?: Record<string, FieldSchema>;
};
Example
{
"MEDIA_PREVIEW": {
"title": "Media Preview",
"iconUrl": null,
"variants": ["standard", "hero"],
"exampleImageUrl": ["https://example.com/preview.png"],
"fields": {
"url": {
"displayName": "URL",
"required": true,
"type": "string"
}
}
}
}
- Respond to /api/pantheoncloud/component_schema/{component_name} with a specific component’s schema
Example
{
"title": "Media Preview",
"iconUrl": null,
"variants": ["standard", "hero"],
"exampleImageUrl": ["https://example.com/preview.png"],
"fields": {
"url": {
"displayName": "URL",
"required": true,
"type": "string"
}
} }
- (Optional) Show a preview of the component when requested at /api/pantheoncloud/component/{component_name}?attrs={ATTRS_BASE64}
Example handler
export default function SmartComponentPreview() {
const { id, attrs } = router.query;
// Decode base64 attributes
const decodedAttrs = attrs && typeof attrs === "string"
? JSON.parse(Buffer.from(attrs, "base64").toString())
: {};
// Get the component from client-side mapping of name to component
const SmartComponent = clientSmartComponentMap[id]?.reactComponent;
return (
<SmartComponent
{...decodedAttrs}
/>
);
}
Render the smart components from the article content
They will be inserted into the content with tag component. For example
// 1. Check if node is a component
if (element.tag === "component") {
const componentType = element.type?.toUpperCase();
const component = smartComponentMap?.[componentType];
if (component?.reactComponent) {
// 2. Render with attributes from tree
return React.createElement(
component.reactComponent,
element.attrs as Record<string, unknown>
);
}
}
A real implementation can be found at https://github.com/pantheon-systems/content-publisher-sdk/blob/main/packages/react-sdk/src/components/ArticleRenderer/PantheonTreeV2Renderer.tsx
Either use the GraphQL search endpoint Implementing Preview for a new website technology or update your search index when webhook events come Implementing Preview for a new website technology
Described in more detail at https://docs.content.pantheon.io/approval-workflows but the short-version is:
- It’s optional
- When enabled, contributors will not be able to publish changes directly to live. They will enter a review queue which can be managed in the collection on the Content Publisher dashboard.
- Approved content can be either published immediately, or approved but “pending publication”
- Admins and collection managers can skip approval for their own content changes.
No configuration is necessary for approval workflows, in any tech stack.