Launch GraphOS Studio

Server-driven UI basics


Server-driven UI (SDUI) is an architectural pattern that aims to reduce client-side logic and provide consistency across client platforms (web, iOS, Android, etc.) by returning product information from an API instead of domain data. This approach is geared toward use cases where collaboration between front-end and back-end teams is possible, and there is a strong desire to build a cohesive experience across multiple teams and organizations.

For advanced patterns and types, see more SDUI topics.

Architectural goals

The primary need for SDUI comes from mobile native-app development. These platforms require shipping versions of your client-side app to an app store that usually has a lengthy review process before a new version gets published. Then, even after a new app version is released, users must update their versions to start using or experiencing the new features.

If your API returns exactly what to display on the screen, you can control the different UI elements or text without app version updates. You can also change the inputs sent to the API without requiring any app updates.

When building a graph with server-driven UI, converging the with the design language used by client teams is often desirable. This requires a definitive design system and a well-defined collection of components to render views on the frontend.

Guiding principles

SDUI is not a framework or library you can install. It's a design pattern you can implement in different ways while still adhering to the same core principles.

Start with UI designs

Start with your UI designs instead of looking at data models or API responses. Take a demand-driven schema design approach to write your preferred query from the client's perspective. The demand-driven concept can apply to any client, but UI applications should have a direct connection from the API response to items on the screen.

If you find yourself describing steps to go from API to views, that's a warning sign that you'll have to duplicate that logic across clients.

Reduce logic in existing client code

Identify and reduce any logic in your existing client code. Some may think of "logic" as only business rules in the backend. However, if you've ever had to update conditionals or switch statements for a new feature, or to update how a feature works, you can consider these frontend portions as "logic," too. Any other places that accept your API response and then transform, concatenate, or use helper functions are additional warning signs for logic.

If you had to change this type of logic in one client, you probably had to change it in others. You can reduce this redundancy by moving the logic to your API.

Return product info, not domain data

Product information includes details about UI components' layout, appearance, and content. Domain data is raw or low-level data representing an application's core information and business logic. Ideally, your server provides specific information related to the presentation and behavior of your UI elements on the client rather than sending domain data.

Instead of enums, booleans, or numbers, you most likely only need your API to return strings. By returning only strings, the server can handle all concerns on the backend and return formatted, localized, and ready-to-display text to the client.

Using nullable strings (String vs String!) is also a best practice. With server-controlled nullability, the client code can directly use the API response—if a is present, it renders that component; if missing, it does nothing.

Use a design system

Once all your clients use your API in the same way, you need a shared language that client apps can use to render their views. This is what design systems are for. With a shared design system, you can plan an API change once and be confident that all clients using the design system can consume it.

Define your capabilities with GraphQL types

Once you have a design system, the next step is to implement it in code. Your server's GraphQL is where you can codify the capabilities of your API. Using GraphQL queries, clients can still opt-in to select only the features or types they support.

Adopt SDUI incrementally

Following all the guiding principles, teams may be tempted to overcomplicate their apps by making everything server-driven all at once. By taking small, manageable steps towards SDUI, your team can balance innovation and stability while enhancing the overall user experience.

For example, you can begin by implementing SDUI for a single UI component or feature within your application. This could be a simple widget or a specific section of your app. This allows you to test the waters and learn how SDUI works in a controlled environment.

You can also use SDUI for new features or sections of your application while keeping the existing frontend intact. That way, you can use SDUI for new functionalities without affecting the legacy parts of your application.

SDUI will speed up feature release time in the future, but the initial adoption requires some overhead.

Schema design

Take, for example, this mock e-commerce , which allows clients to display a list of featured products:

type Product @key(fields: "id") {
id: ID!
name: String
formattedPrice: String
# other product fields...
type ProductsCarousel {
products: [Product]
count: Int
type ProductsList {
products: [Product]
count: Int
type ProductsError {
message: String!
union FeaturedProductsResponse = ProductsList | ProductsCarousel | ProductsError
type Query {
featuredProducts: FeaturedProductsResponse

While querying Query.featuredProducts, the client can render multiple specific experiences depending on the query result. You can use the returned __typename value to determine whether to render a collection of products or show the user an error message.

fragment productFields on Product {
query FeaturedProductsCollection {
featuredProducts {
... on ProductsList {
products {
... on ProductsCarousel {
products {
... on ProductsError {

Check out SDUI schema design for more design patterns.

Edit on GitHubEditForumsDiscord