Skip to main content

Get Standalone View Models

Standalone view models don't require a content ID, making them ideal for computed data, global configurations, navigation structures, or aggregated information that isn't tied to a specific content item. Unlike content-backed view models which layer on top of Get, standalone view models are queried directly from the root View field.

A standalone view model is backed by your endpoint rather than a content item—it extends ViewModel<YourEndpoint>. That gives it a natural home for cross-cutting presentation logic: it can run queries, aggregate data from multiple sources, and accept request parameters, all without the caller supplying an ID.

Configuration

Register the view model with endpointViewType, choosing whether it appears under the query or mutation root:

1
import java.util.List;
2
import java.util.stream.Collectors;
3
4
import com.psddev.cms.view.ViewInterface;
5
import com.psddev.cms.view.ViewModel;
6
import com.psddev.dari.db.Query;
7
import com.psddev.dari.web.annotation.WebParameter;
8
9
@ViewInterface
10
public class NavigationViewModel extends ViewModel<ContentApiEndpoint> {
11
12
@WebParameter
13
private Integer limit;
14
15
/**
16
* The most recent article headlines for the site navigation.
17
*/
18
public List<String> getRecentHeadlines() {
19
return Query.from(Article.class)
20
.sortDescending("publishDate")
21
.select(0, limit != null ? limit : 5)
22
.getItems()
23
.stream()
24
.map(Article::getHeadline)
25
.collect(Collectors.toList());
26
}
27
}
28
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
}
tip

Use OperationType.MUTATION for endpoint view models whose logic performs writes—they'll appear under the mutation root instead, ensuring consumers (and tooling) treat them as side-effecting operations.

Querying a standalone view

Each registered view model becomes a field under the root View, named after its class with the ViewModel suffix removed (NavigationViewModelNavigation). The field returns the view directly—no ... on fragment or content lookup needed—with the view's fields under data:

1
query Navigation($limit: [String!]) {
2
View {
3
Navigation(typed: {parameters: {all: {limit: $limit}}}) {
4
data {
5
recentHeadlines
6
}
7
}
8
}
9
}
10

Parameters work the same way as content-backed views: the typed argument carries values for the view model's annotated fields (as lists of strings, mirroring HTTP parameters), and an untyped request argument is available as a generic alternative.

When to choose standalone vs. content-backed

Use a standalone view when...Use a content-backed view when...
The data isn't anchored to one content item (navigation, dashboards, aggregations).You're presenting a specific piece of content (an article page, a product detail).
Callers shouldn't need to know any IDs.The caller naturally has an ID, URL, or unique key.
You're composing data from many queries or external systems.You want editorial features like Preview to flow through the view.

The two compose well: a front end's page query often fetches one content-backed view for the main content plus standalone views for navigation and site chrome, all in a single GraphQL request.

Next steps

Was this page helpful?

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