Using Prisma with Plasmic

Authors
  • avatar
    Link
    Alex Noel
Last updated
 

Plasmic has no problem displaying data of any kind, and among the many available data sources Prisma is our favorite pick. It plays extremely well with large and structured data, and allows you to manage your data in a way that is both safe and efficient.

This guide walks through configuring the integration and using it to query Prisma/PostgreSQL data through Plasmic’s visual query builder. It also works as a reference for building a more custom integration on top.

Architecture diagram

Configure Prisma

You can deploy the starter by clicking the Vercel button in the README.md or run the project locally first. If you are cloning the repo and running it yourself locally, install the dependencies first:

pnpm install

The build script runs prisma migrate deploy before next build, so we need to create the database and set up your environment:

  1. Create a Prisma Postgres instance with pnpm dlx prisma init --db.
  2. Create a local .env file.
  3. Put the generated DATABASE_URL into that file.
  4. Generate an AUTH_SECRET with pnpm dlx auth secret and add that too.
  5. Run the initial migration with pnpm dlx prisma migrate dev --name init.
  6. Generate the Prisma Client with pnpm dlx prisma generate.
  7. Seed the database with pnpm dlx prisma db seed.

Your local .env should end up looking roughly like this:

DATABASE_URL="postgres://USER:PASSWORD@HOST:5432/postgres?sslmode=require"
AUTH_SECRET="YOUR_RANDOM_SECRET"

You can then run pnpm dlx prisma studio to confirm that the data is in place, or use Prisma Console in cloud. You should see the following tables:

Prisma DB view

Seeding the data

The Prisma schema in this repository already defines User, Role, and Post, together with the Auth.js tables for accounts and sessions. The seed script creates three roles:

  • guest, which can read
  • reader, which can read
  • writer, which can read, create, update, and delete

It also creates sample users and posts. You can inspect a post list, open a post page, and verify that two users with different roles do not behave the same way.

In a fresh seed:

  • bob@example.com / password123 is the writer account
  • alice@example.com / password123 is the read-only account

Create a Plasmic project

If you do not already have a Plasmic project for this starter, create an empty one in Plasmic Studio first. The only things you need at this point are the project ID and public token.

Point the app at your Plasmic project

Edit plasmic-init.ts and replace the starter’s project ID and public token with your own:

  • Project ID can be found in the project URL
  • Public token can be found from the Code button in the top-right corner of the project.
export const PLASMIC = initPlasmicLoader({
  projects: [
    {
      id: 'YOUR_PROJECT_ID',
      token: 'YOUR_PUBLIC_PROJECT_TOKEN',
    },
  ],
  preview: true,
})

Hint: The starter leaves preview: true on. That is convenient during development, but don’t forget to turn it off in production.

Run the app

Once the project ID and public token are in place, start the development server:

pnpm dev

Then open http://localhost:3000/plasmic-host. This will confirm if the app is running normally.

Configure your Plasmic app host

App host is a bridge between your codebase and Plasmic that passes your registered items into Studio:

  • Data-fetching component - PrismaDataFetcher
  • Auth context - UserSession
  • Data query - prismaQuery

Once the app is running, you need to set the app host in your Plasmic project. Localhost is fine while you are still changing registration metadata. Once other people need the project, use a deployed host URL instead.

Plasmic app hosting

If the project reloads after you save the host URL, the connection is working.

Build a posts list page

Create or open a page in Plasmic that will serve as the blog index. Then add a page data query named Get Recent Posts with these settings:

  • Function: prismaQuery
  • Table: Post
  • Operation: findMany
  • Where: published equals true
  • Include Relations: author
  • Order By Field: createdAt
  • Order Direction: desc

Execute the query once in Studio and inspect the returned data.

Posts data query

You should see an array under $q.getRecentPosts?.data. Bind a repeated container to that array, then bind the fields inside the repeated item to the current post’s title, content, and author.name.

Posts page bindings

In short: records come from Prisma, page structure is configured in Plasmic, and the core fetching logic lives in code.

Use the same pattern for /posts/[id]

The repository already includes a route for this in app/posts/[id]/page.tsx. That file uses generateStaticParams() to fetch published posts from Prisma and prebuild their paths.

On the Plasmic side, create a page with the path /posts/[id]. Give id a preview value that exists in the database, such as 1 in a fresh seed. Then add a page query, for example Get Post, and filter Post by that same id parameter.

Individual post page

How this works at runtime:

  1. Next.js resolves the route.
  2. Plasmic receives the page param.
  3. The query uses that param to fetch one record.
  4. The page binds its fields to the result during SSR/SSG phase.
  5. The user sees the fully rendered page on first paint.

Configure dynamic metadata for posts

To make the posts more SEO-friendly, you can configure the page’s metadata to use the post’s title and content. In Plasmic Studio, go to the page settings and set the title and description fields to bind to $q.getPost?.data.title and $q.getPost?.data.content, respectively.

See this video for a quick walkthrough:

Using write operations

You can also use prismaQuery() in interactions for writing/updating/deleting items.

For example, let’s implement a functionality to create posts:

  • Create a simple form with two inputs and a button. The inputs will be for the post title and content, and the button will trigger the creation of a new post.

  • Add an interaction to a button that calls the prismaQuery function with the create operation on the Post table.

  • Bind the input fields to the values you want to create in the data field.

Create post interaction

When the button will be clicked, the interaction will execute the query and create a new post in the database. You can then call Refresh Data action to see the new post appear.

How things work under the hood

Registering data queries and custom functions

The interesting part of this starter is that prismaQuery() is registered with enough metadata that Studio can present it as a query builder instead of a bare function call.

The function itself lives in functions/prismaQuery.tsx, and the Studio-facing registration lives in plasmic-init.ts:

PLASMIC.registerFunction(prismaQuery, {
  name: 'prismaQuery',
  isQuery: true,
  isMutation: true,
  params: [
    prismaTableParam,
    getPrismaOperationParam(PrismaOperations),
    // where, orderBy, pagination, select/include, cursor, distinct...
  ],
})

Here are a few important things to highlight about the registration:

  • isQuery: true is what makes prismaQuery() appear in the Page Data panel as a custom data query.
  • isMutation: true is what makes it appear in the Interactions section as a data query.
  • Table names and field options come from an API route that reads Prisma Client’s runtime data model
  • Things like select and include are automatically hidden because Prisma forbids using both together
  • Filtering, sorting, pagination, cursoring, and distinct selection are exposed as structured controls instead of a single free-form blob

Auth contexts

The starter registers a UserSession global context in plasmic-init-client.tsx. That gives Plasmic access to session data and to global login and logout actions.

Login form

For local development, auth.config.ts loosens the cookie settings so app-host preview can work across tabs. That is why you can log into the local app in one tab and then see authenticated state inside Studio in another.

That is not meant for production — those cookie settings are turned off for security. Use localhost when you need to verify authenticated behavior inside Studio. Use the deployed host for normal shared editing.

logged in user session

From here, the decisions are project-specific: which models users can access, how queries are configured and rendered, and which interactions belong in the code.

Permissions

Before prismaQuery() executes a Prisma operation, it calls checkPermissions() on the server. That function reads the current Auth.js session, resolves the user’s role, and maps the requested Prisma operation to a CRUD permission.

So Plasmic can describe a query, but it cannot authorize it for safety reasons — auth should always be safely handled in code.

Permission check flow diagram

In the seeded roles:

  • guests can read
  • readers can read
  • writers can read, create, update, and delete

You can create an admin UI to modify the permissions, or change it directly through Prisma. If the current user is not allowed to perform an operation, prismaQuery() returns an error and never reaches Prisma.

How to fetch data only on client-side

Unlike the data queries, PrismaDataFetcher component is limited to read operations in this starter. If you know what you’re doing and you don’t want to call the prismaQuery() function directly, you can use the element actions pattern to call your API endpoints using interactions.

PrismaDataFetcher, registered in plasmic-init-client.tsx, wraps prismaQuery() and exposes a DataProvider with three values:

{
  data, error, loading
}
Data fetching component

Usually you would want to use it for situation where SSR is not needed because data changes frequently and you always want to display the most up-to-date version. For example:

  • A component that displays user subscriptions and needs to reflect changes immediately after the user adds or removes one.
  • A dashboard that shows the current number of active users, and needs to update that number in real time.
  • A feed of recent comments that should show new comments as they come in, without requiring a page refresh.

FAQ

pnpm build throws an error P3005

Error: P3005

The database schema is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline

In this case you need to define the baseline migration for your database once:

pnpm dlx prisma migrate resolve --applied 20250722115601_init

$q.MyQuery.data variable is undefined

When you are fetching the data sometimes the variable that should contain the data is undefined. This can be caused by a few different things:

  • The query has not been executed yet. Make sure to execute the query in Plasmic Studio and check if there are any errors in the query configuration.
  • The query is returning an error. Check the error property of the query result to see if there are any issues with the query execution.
  • The query is not returning any data. Check the database to ensure that there are records that match the query criteria.
  • There is a mismatch between the query configuration and the database schema. Double-check the query parameters and ensure they align with your Prisma schema.

To prevent the fatal error we highly suggest to use optional chaining when accessing the data variable, like this:

$q.MyQuery?.data

Try it out!

If you haven’t already, try out the starter by clicking the Vercel button in the README.md or run the project locally. Then you can start building your own Plasmic + Prisma app on top of it!

Follow @plasmicapp on Twitter for the latest updates.