Plasmic
In this guide, we will show you how to build a crowd-sourced Pokemon Pokedex, by connecting Supabase, an open source Firebase backend alternative, with Plasmic, a visual builder for the web. While many users leverage Plasmic to quickly launch and iterate on landing pages, in this tutorial we’ll show just how powerful Plasmic can be as a general-purpose visual builder for React, which can be used to design and implement fully featured read-write applications.
You can play with the live demo here: https://plasmic-supabase-demo.vercel.app/
You can also take a look at the Plasmic project here: https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6
You’ll need to enable 3rd-party cookies in your browser for the project to properly load.
At a high level,
- Supabase is used to store the database of Pokemon (backed by Postgres) and provides an authentication backend. Our code base includes React components for querying the database, displaying this data, and supporting user sessions.
- Plasmic is used to create the pages and visual design of the application. We import our Supabase components into the Studio, which can be visually assembled and configured there (e.g. for displaying data).
- Plasmic designed pages are rendered back into the Next.js application.
Step 1: Set up your Backend on Supabase#
- On the Supabase dashboard, click
New project
and set the name of the project. By default, Supabase will already be set up for user signups with email, with users being stored in ausers
table.
- Navigate to the
Table Editor
on the left side navigation bar. Here we can create aNew table
to store our Pokemon entries. Make sure you are in theschema public
view. Create a new table calledentries
, with 6 columns:id
: is a unique ID for the entry. This column should be generated automatically as the primary column.user_id
: Create a relation to theuser
table by clicking on the link icon next to the column name. Here, you can select theid
column of theuser
table.name
,description
,imageUrl
: This will store the name, description, and imageUrl for each Pokemon.inserted_at
: This will be an automatically populated column, set to when the row was first inserted.
Note: In this tutorial we’ve turned off “Row Level Security (RLS)”. In practice, you will want to create policies that restrict who gets to create, edit, and delete posts. By turning this off, any user can modify the database without restrictions.
For your convenience, feel free to import the following CSV into Supabase to pre-populate your database. In order to import, you must select Import data via spreadsheet
, in the new table dialog box. (It does not work on existing tables.)
Step 2: Set up your codebase#
We have a working code example for you here. This starter comes with all of the code components you need to get started querying Supabase through Plasmic Studio.
Code components are React components defined in your code base that we import into Plasmic Studio for use. Your project will be configured to look for these at
http://localhost:3000/plasmic-host
You can use these components in your design, as well as style them. Check outsupabase-demo/plasmic-init.ts
to see how they are registered with Plasmic.
First, clone the repo to your development machine and install the dependencies.
1git clone git@github.com:plasmicapp/plasmic.git 2cd plasmic/examples/supabase-demo/ 3yarn install
Copy .env.example
to .env.local
, which will store the environment variables when running a local development server. Add your Supabase project’s URL and public key, which you can find in the API
tab on the left pane of your Supabase dashboard.
Now run the dev server, which listens at http://localhost:3000
1yarn dev
Step 3: Explore the existing application#
Navigate to http://localhost:3000 in your web browser. The project will already be set up for user signups, logins, and an admin interface for adding and editing Pokemon to the database. Feel free to sign up with your email address for an account and add Pokemon to the database. Supabase will require that you verify your email address before you can log in.
If you pre-populated the database in Step 1. you should see the following homepage after logging in. Otherwise, feel free to add Pokemon manually via the UI.
Step 4: Clone the Plasmic project#
Now let’s try to make some additions! The code base is currently configured to a read-only copy of the Plasmic project. Let’s make an editable copy first.
Open the default starter Plasmic project here: https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6
To make an editable copy, click on the Copy Project
button in the blue bar. This will clone the project and redirect you to your copy.
Step 4a: Configure your code base to use the new Plasmic project#
Take note of the project ID
and API token
. You can find the project ID in the URL:
https://studio.plasmic.app/projects/PROJECTID
.
The API token can be found by clicking the Code
button in the top bar.
Now go back to .env.local
and update the corresponding project ID and token fields.
Step 4b: Configure your Plasmic project app host#
To tell Plasmic to look for your code components on your dev server, you’ll need to update your project’s app host to http://localhost:3000/plasmic-host
.
Note: At this point, you’ll need to keep your dev server running at
http://localhost:3000
for the project to load.
After restarting the dev server and Plasmic Studio, you should now be able to make edits across Plasmic Studio and your codebase.
Step 5: Create a new page for our Pokedex gallery#
Let’s make a visual gallery for our Pokemon by using the code components from the code base.
Create a new page called Gallery
, and set a path for this page (/gallery
).
Insert a SupabaseGrid
by searching the AddDrawer (by clicking the blue + button)
For source see
components/CodeComponents/DatabaseComponents.tsx
Then in the right-hand panel, configure the props on SupabaseGrid
.
tableName
should match the table you created in SupabasetableColumns
are a comma-delimited list of columns you want to select from the table- We also set the number of columns and spacing shown in the grid
The SupabaseGrid
will loop over the rows from the query.
Now customize the repeated content by inserting instances of SupabaseField
. Select the type of content and a selector string to fetch a single value. In the example below, we use {{row.imageUrl}}
to retrieve the imageUrl
column of the row. Apply any styling and layout you want on these elements.
Putting it all together (video)#
For your convenience, the following video shows you how to create the page end-to-end.
Step 6: Check your dev server#
If you have been running your development server this whole time, you’ll see that we have been automatically fetching and rebuilding your site as you make changes in Plasmic Studio. If you need to restart your dev server, just run:
1yarn dev
See the results at http://localhost:3000/gallery
.
How does this all work under the hood?#
SupabaseGrid#
SupabaseGrid
is a code component that was registered in plasmic-init.ts
. The props
field is used to tell the Plasmic Studio the component prop interface, which allows us to expose these props in the right pane as shown in the screenshots earlier. See the docs for details on component registration.
// plasmic-init.ts
...
PLASMIC.registerComponent(SupabaseGrid, {
name: "SupabaseGrid",
props: {
tableName: "string",
tableColumns: "string",
queryFilters: "object",
children: {
type: "slot",
defaultValue: {
type: "text",
value: "Placeholder",
},
},
numColumns: {
type: "number",
defaultValue: 4,
},
columnGap: {
type: "number",
defaultValue: 16,
},
rowGap: {
type: "number",
defaultValue: 16,
},
count: "number",
loading: {
type: "slot",
defaultValue: {
type: "text",
value: "Loading...",
},
},
},
importPath: "./components/CodeComponents/DisplayCollections",
});
SupabaseQuery#
SupabaseGrid
wraps a SupabaseQuery
component, where we perform the query based on the provided props and store the result in a SupabaseQueryContext
. This will be used in downstream components to display the data.
// supabase-demo/components/CodeComponents/DatabaseComponents.tsx
export function SupabaseQuery(props: SupabaseQueryProps) {
// These props are set in the Plasmic Studio
const { children, tableName, columns, className, filters, single } = props;
const [result, setResult] = React.useState<any[] | undefined>(undefined);
...
// Performs the Supabase query
let query = supabase.from(tableName!).select(columns + ",id");
query = applyFilter(query, validFilters, contexts);
const { data, error, status } = await (single ? query.single() : query.order('id', { ascending: false }));
if (error && status !== 406) {
throw error;
} else if (data) {
setResult(data);
}
...
// Save the result in a `SupabaseQueryContext for use with downstream components
return (
<div className={className}>
<SupabaseQueryContext.Provider value={result}>
{children}
</SupabaseQueryContext.Provider>
</div>
);
}
Note that this code component is defined in your codebase. Feel free to augment it to expose more powerful querying capabilities to the Plasmic Studio.
SupabaseGridCollection#
SupabaseGrid
also nests a SupabaseGridCollection
under the SupabaseQuery
. This code component is a simple CSS grid, where we retrieve the Supabase query results from SupabaseQueryContext
, and iterate over the results. For each row, we populate a RowContext
, which will be used by the children to read the results of a single row. Note the use of repeatedElement
, a special convenience function that enables the component’s children to be repeated. In this case, this represents a single card to be shown in the gallery.
// supabase-demo/components/CodeComponents/DisplayCollections.tsx
export function SupabaseGridCollection(props: SupabaseGridCollectionProps) {
const supabaseQuery = React.useContext(SupabaseQueryContext)
const { children, columns, columnGap, rowGap, count, className, loading, testLoading } = props
const result = supabaseQuery
if (!result || testLoading) {
return loading
}
return (
<div
style={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, 1fr)`,
columnGap: `${columnGap}px`,
rowGap: `${rowGap}px`,
}}
className={className}
>
{result.slice(0, count).map((row: any, i: any) => (
<RowContext.Provider value={row} key={row.id}>
<div key={row.id}>{repeatedElement(i === 0, children)}</div>
</RowContext.Provider>
))}
</div>
)
}
SupabaseField#
SupabaseField
will either render a SupabaseTextField
or SupabaseImgField
depending on the type. These code components simply read a single value from the contexts and display the data.
// supabase-demo/components/CodeComponents/DisplayCollections.tsx
export function SupabaseTextField({ name, className }: { name?: string; className?: string }) {
const contexts = useAllContexts()
if (!name) {
return <p>You need to set the name prop</p>
}
return <div className={className}>{getPropValue(name, contexts)}</div>
}
In summary, by populating state into React contexts, we can store and retrieve data for use in other code components, which can be used for arbitrarily powerful interactions in Plasmic Studio.