Skip to main content

Get Content-Backed View Models

Content-backed view models are accessed through the Get operation's View field, allowing you to fetch transformed presentation data for a specific content item. This approach layers view model transformations on top of content fetching—you identify the content item with the usual with lookup, then select a view model representation of it.

This page assumes familiarity with the Get operation; you'll use the same configuration and query structure with the addition of the View field. The same editorial features available on content—most notably Preview—work seamlessly with content-backed view models, allowing you to render unpublished changes through the exact same presentation logic as live content.

Configuration

Write a ViewModel<M> class for your content type, annotate it with @ViewInterface, and register it with entryViewClass:

1
import com.psddev.cms.view.ViewInterface;
2
import com.psddev.cms.view.ViewModel;
3
import com.psddev.dari.web.annotation.WebParameter;
4
5
@ViewInterface("ArticleView")
6
public class ArticleViewModel extends ViewModel<Article> {
7
8
@WebParameter
9
private Integer summaryLength;
10
11
public String getHeadline() {
12
return model.getHeadline();
13
}
14
15
public String getSummary() {
16
String body = model.getBody();
17
if (body == null) {
18
return null;
19
}
20
int length = summaryLength != null ? summaryLength : 160;
21
return body.length() <= length ? body : body.substring(0, length) + "…";
22
}
23
24
public Boolean getRecentlyPublished() {
25
return model.getPublishDate() != null
26
&& model.getPublishDate().getTime()
27
> System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L;
28
}
29
}
30
1
import java.util.Set;
2
3
import com.psddev.dari.db.Singleton;
4
import com.psddev.graphql.gca.GCAEndpoint;
5
import com.psddev.graphql.gca.GCASchemaSettings;
6
import com.psddev.graphql.gca.settings.OperationType;
7
8
public class ContentApiEndpoint extends GCAEndpoint implements Singleton {
9
10
@Override
11
public Set<String> getPaths() {
12
return Set.of("/content-api");
13
}
14
15
@Override
16
protected GCASchemaSettings getSchemaSettings() {
17
return GCASchemaSettings.newBuilder()
18
.readonlyEntryClass(Article.class)
19
.entryViewClass(ArticleViewModel.class)
20
.endpointViewType(OperationType.QUERY, NavigationViewModel.class)
21
.build();
22
}
23
}

Querying a view

The View field appears on Get results for any type that has registered view classes. It takes a required of argument—a one-of input selecting the view by schema type name (an enum of the available views) or fully-qualified Java class name—and returns a union of the record's possible view types:

1
query GetArticleView($with: RecordGetInput!, $request: Record__ArticleView__RequestInput) {
2
Get {
3
Record(with: $with) {
4
View(of: {type: ArticleView}) {
5
... on Record__ArticleView {
6
Response(typed: $request) {
7
data {
8
__typename
9
_id
10
headline
11
summary
12
recentlyPublished
13
}
14
}
15
}
16
}
17
}
18
}
19
}
20

The pattern is: View(of: ...)... on Record__<ViewType>Response(...)data → your view's fields.

Passing parameters

View models frequently accept input—a summary length, a locale, a pagination cursor. Declare fields on your view model with web request annotations (like @WebParameter), and the GCA exposes them through the Response field's arguments:

  • typed — a view-specific input type (e.g. Record__ArticleView__RequestInput) whose parameters/all map carries your parameters by name. Values are lists of strings—exactly like HTTP request parameters—and are coerced to the Java field types.
  • request — an untyped equivalent shared by all views, useful when building queries dynamically.

This mirrors how the same ViewModel classes behave when rendering web pages: the GCA constructs a synthetic web request from your GraphQL arguments, so view models work identically in both worlds.

Previewing through views

Because view models are the contract your front end consumes, previewing unpublished content is most useful through the view. The Preview field on Get results (requires includeGetPreview()) exposes its own View field, so the same query shape renders a shared preview instead of the live record:

1
{
2
Get {
3
Record(with: {_id: "..."}) {
4
Preview(id: "...") {
5
View(of: {type: ArticleView}) {
6
... on Record__ArticleView {
7
Response {
8
data {
9
headline
10
summary
11
}
12
}
13
}
14
}
15
}
16
}
17
}
18
}

This is the foundation for real-time preview in headless front ends: the editor's in-progress changes flow through your exact production presentation logic.

Accessing the backing model

By default, a view exposes only what its getters declare—that's the point. If consumers also need raw model access alongside the view, enable includeModelOnViews() to add a _model field to every view type. For debugging, allowRawViewAccess() adds a _raw field returning the whole view as a map—watch out for cyclic references, and consider excludeTypeNamesFromRawView() if type names are sensitive.

Next steps

Was this page helpful?

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.