Adding Custom Branding to a Redwood App


Many organizations want some ability to use a service to handle some of their functionality and customize the interface users are shown. This includes things like the names they see displayed, data they want to be shown, or some images they want to see. Giving them the ability to add their own branding is one way to add value to your own products.

In this Redwood tutorial, we'll make an app that will change formats depending on what user is associated with the page.

Create the Redwood app

First thing we need to do is spin up a new app. In a terminal, run:

1yarn create redwood-app branding

When this is done, you'll have a bunch of new files and folders in a branding directory. The main folders we'll be working in are the api and web folders. We'll start with some work in the api folder first.

Setting up the model

Building our app by making the models for the database schema works really well in Redwood. I usually like to start here because it's one way you can start thinking about your business logic from the beginning.

We'll be using a Postgres database. Here are the docs to get Postgres installed locally. Let's start by updating the .env file with a connection string for your local instance. Uncomment the DATABASE_URL line and update the value. It might look something like this.


Now we can go to api > db and open the schema.prisam file. This is where we'll add our models. One thing we need to do is update the provider value at the top to postgresql instead of sqlite. Next, you can delete the existing example model and add these.

1model User {
2 id Int @id @default(autoincrement())
3 name String
4 info Info[]
5 image Image[]
6 layout Layout[]
9model Image {
10 id Int @id @default(autoincrement())
11 name String
12 url String
13 user User @relation(fields: [userId], references: [id])
14 userId Int
17model Info {
18 id Int @id @default(autoincrement())
19 balance Float
20 lastLogin DateTime
21 endDate DateTime
22 user User @relation(fields: [userId], references: [id])
23 userId Int
26model Layout {
27 id Int @id @default(autoincrement())
28 name String
29 dataLocation String
30 imageUrl String
31 user User @relation(fields: [userId], references: [id])
32 userId Int

Usually, when you have relationships between tables like we have here, it's a good idea to seed your database with some initial values. You'll see this pretty often with apps that have dropdown menus or pre-defined user roles.

We'll be adding our own seed data in the seed.js file. You can open that and delete all of the commented-out code in the main function and replace it with this.

1await db.user.create({
2 data: { name: 'Nimothy' },
5await db.image.create({
6 data: {
7 name: 'Nimothy Profile',
8 url: '',
9 userId: 1,
10 },
14 data: {
15 balance: 7.89,
16 lastLogin: new Date(),
17 endDate: new Date(),
18 userId: 1,
19 },
22await db.layout.create({
23 data: {
24 name: 'MidLeft',
25 dataLocation: 'mid-left',
26 imageUrl:
27 '',
28 userId: 1,
29 },

Run migration

With our models and seed data in place, we can migrate the database with this command:

1yarn rw prisma migrate dev

That will add the tables and columns with the defined relationships to your Postgres instance. To seed the database, we'll need to run:

1yarn rw prisma db seed

This will add the placeholder data we created in seed.js so that the relationships between tables and columns are met and don't cause errors with our app.

Since we've run the migration and seeding, we can move on to the back-end and front-end.

Making the back-end and front-end

We're going to make the functionality to add new layouts and new users to the app for now so that we can show how things update for the user. We'll also be adding a special page to show how these updates would actually affect users.

For the sake of this project, we're going to assume that adding new users and layouts is admin functionality that users of the app won't be able to see. Later on, we'll add the user view that applies the custom branding.

Adding the ability to create and update users and layouts only takes a couple of commands in Redwood. Let's start by making the users functionality with this:

1yarn rw g scaffold user

This will generate the back-end GraphQL types and resolvers as well as adding new components to the front-end. We'll run this command one more time for the layouts functionality:

1yarn rw g scaffold layout

You can take a look at the code Redwood generated to make all of this work on the front-end by going through the web > src directory. There are new files under components, layouts, and pages, plus Routes.js has been updated. All of the new files you see were created by that scaffold command for those two models.

The back-end code that supports new user and layout creation and the edit and delete functionality can be found in the api > src directory. You'll see new files under graphql and services that hold the GraphQL types and resolvers that make all of the CRUD work and persists the data.

Now we have the CRUD for the front-end and back-end for these two models. You can run the scaffold command to create the CRUD for the other models, but we don't actually need it. What we do need are the types for those models. We can generate those with a couple of Redwood commands:

1yarn rw g sdl info
2yarn rw g sdl image

The sdl generator makes all of the GraphQL types and a resolver for the specified model. If you check out api > src > graphql, you'll see the new types that were generated for info and images. Then if you look in api > src > service, you'll see that a resolver has been made to handle a query for us for both info and images.

The reason we're adding these types is so the user types reference these so we need them to be available, even if we aren't adding the front-end piece.

Running the updated app

If you run your app with yarn rw dev and navigate to localhost:8910/users, you'll see a table and buttons for different ways to interact with the data. You should see something similar to this:

Go ahead and add a new user by clicking the "New User" button. This will open the form like this:

Now you can add a new layout for this new user by going to localhost:8910/layouts and clicking the "New Layout" button. It'll bring up this form:

Show the user their custom view

Now that we've got the core functionality together to create users and associate layouts with them, we can create the custom view that they will see. To do that, we'll use Redwood to generate a page that will load a specific user's layout. Make a new page with this command:

1yarn rw g page option

This will add a new page to the web > src > pages directory and it will update the Routes.js file with a new /option route. If you navigate to localhost:8910/option, you'll see this:

We need to update this page to show the user's layout by pulling some data from the back-end.

Querying for the user layout

In the web > src > pages > OptionPage directory, open the OptionPage.js file and add the following import to get your GraphQL query ready.

1import { useQuery } from '@redwoodjs/web'

Then at the bottom of the file, right above the export statement, add this code for the query.

1const LAYOUT = gql`
2 query layout($id: Int!) {
3 layout(id: $id) {
4 id
5 name
6 dataLocation
7 imageUrl
8 userId
9 }
10 }

This will give us a specific layout based on the id we pass to the query. We'll be manually setting this id to mimic what we might get from a prop from a different component. We'll add the variable for the id in our query hook. This will be added inside of the OptionPage component:

1const { loading, data } = useQuery(LAYOUT, {
2 variables: { id: 1 },
5if (loading) {
6 return <div>Loading...</div>

We're using the useQuery hook to execute the query we made earlier and we're manually setting the id of the layout we want to use. Then we're checking the load status for the data and rendering an indicator that the page is loading the content so that the user doesn't see an error before the fetch finishes.

The last thing we'll do is update the elements to show in the layout format we currently have loaded.

Updating the page

To show the right layout, we're going to install the styled-components package. That way we'll be able to pass props to update the layout based on the user viewing the page. So in the web directory in your terminal, run:

1yarn add styled-components

Now we're going to import that package in the OptionPage.js file.

1import styled from 'styled-components'

Then we need to add a new styled component to handle the location of the image based on that user layout. We'll add this right above the OptionPage component.

1const Img = styled.img`
2 display: block;
3 position: absolute;
4 top: ${(props) => (props.dataLocation === 'mid-left' ? '35%' : 'unset')};
5 right: ${(props) => (props.dataLocation === 'mid-left' ? 'unset' : '0')};
6 width: 360px;

We're doing a simple update of the image location with an absolute position setup. This will let the image move independently of the other elements on the page so that the user sees it in the place they've selected. We're passing in the dataLocation value as a prop.

Cleaning things up

Just a few finishing touches and we'll have this layout working. First, we need to add the Img to OptionPage. We'll delete the existing Link from the return statement and add this image instead.

1<Img src={data.layout.imageUrl} dataLocation={data.layout.dataLocation} />

We'll also add a little line to show the name of the current layout. This will go below the description of the file location.


That's it! We've finished up this app. Now if you run the app with yarn rw dev, you should see something similar to this.

If you update the id in the query variable to 2 and reload the browser, you'll see something like this.

Finished code

If you want to check out the complete code, you can check it out in the custom-app-branding folder of this repo. You can also check out the front-end in this Code Sandbox.


If you're interested in a deeper dive on how Redwood handles scaffolding or the general way it creates files for you, make sure to go through their docs.


Software Team Lead

Milecia is a senior software engineer, international tech speaker, and mad scientist that works with hardware and software. She will try to make anything with JavaScript first.