Skip to main content
Episode 15 of 24Back office55 min

Build a locked admin panel to manage products

Create an admin area only the admin role can open, where you add and edit products, variants and categories, upload images, publish drafts and see an audit log.

You will have: An /admin area where you manage the catalogue yourself, protected on every page and every action, with every change recorded in an audit log.

Before you start

  • Episodes 8, 9 and 11 are done: the catalogue, Cloudinary uploads and login with roles work
  • Know the email address you will use for your admin account, and choose a long, unique password for it
  • Have a few real product details at hand: names, prices, sizes or variants, stock and photos
  • Keep your Cloudinary keys in .env.local as set up in Episode 9

Make it yours

Change the example (fashion store) to your own. Your answers are saved in this browser and used in every episode.

Other series keywords (5)

Your prompt

Paste into: Your AI agent opened in your project folder.
Context: this project is the online store "Thread & Needle", a fashion e-commerce store selling women's and men's clothing, shoes and accessories in United States and charging in USD. It uses Next.js (App Router), TypeScript, Tailwind CSS, PostgreSQL with Drizzle (local database shop_local), Zod and Auth.js with a role column (customer or admin). Product pages, Cloudinary signed uploads (Episode 9), accounts, checkout, payments and email already exist.

Goal: build an admin area at /admin where I manage categories, products and variants. Before coding, read the docs for the Next.js version in this project, especially how routing protection (middleware or proxy), server actions and route handlers work in that version, because they change between versions.

Plan first: show me your plan (routes, files, any database changes) and wait for my OK before changing anything. Explain "role", "server action" and "audit log" in a few plain words.

Tasks, after I approve:
1. Access control, in layers. Layer one: block /admin for anyone who is not signed in as an admin. Layer two, and the one that really matters: check the admin role again inside every server action, route handler and data query used by the admin. Never rely on hiding links. Non-admins get a plain not-found or forbidden page that reveals nothing.
2. Creating the first admin: do not build a public way to become admin. Write a one-time script that promotes an existing account whose email I type in the terminal. Show me how to run it against shop_local only.
3. Categories: list, create, edit, archive. Unique readable slugs.
4. Products: list with search, status filter and pagination; create and edit forms with name, slug, description, category, status (draft or published), and images. Only published products appear in the shop. Drafts are visible to admins only.
5. Variants (for example size and colour, or whatever fits women's and men's clothing, shoes and accessories): each has its own SKU, price, stock count and optional image. Money is stored as whole numbers in the smallest unit of USD; the form lets me type normal amounts. Block negative prices and negative stock.
6. Images: reuse the existing Cloudinary signed upload from Episode 9, so the upload signature is created on the server and only for admins. Validate file type and size, and store the image id and delivery URL.
7. Deleting: products that appear in any order must be archived, never deleted, so old orders stay intact.
8. Validation: every form and action uses a Zod schema on the server. Return clear field errors. Escape or reject unsafe content in descriptions.
9. Audit log: a table recording who (admin id), what (create, update, publish, archive), which item, a short before-and-after summary and the time. Never record passwords, tokens or keys. Add a simple read-only audit log page.
10. Keep the admin pages simple, mobile friendly and consistent with our design system.

Security rules: no secrets in code or logs; do not trust ids or prices from forms without validation; protect every state-changing action against cross-site request forgery as the framework recommends; return generic errors.

Verify yourself and show results: Vitest tests proving a customer account and a signed-out visitor are refused by each admin action; tests for the product Zod schema; then run the type check and linter. Start the site, create one draft product with two variants and an image, publish it, and confirm it appears in the shop only after publishing and that the audit log shows both steps.

Commit to a branch named feature/admin-products with a clear message.

Report back in this format: 1) What was done. 2) What I should check in the browser. 3) Anything that needs my decision.

Review the prompt, then copy it. Never paste real passwords or API keys into a chat.

How to check it worked

  • Opening /admin while signed out, or signed in as a normal customer, never shows the admin pages
  • You can create a draft product with two variants and an image, and it is invisible in the shop until you publish it
  • Submitting the form with a negative price or empty name shows clear error messages and saves nothing
  • A product that has orders can be archived but not deleted
  • The audit log lists each create, edit and publish with your admin email and a time
  • The agent shows passing tests that non-admin users are refused on every admin action

Sample result

Example: fashion store

Example admin product list for the fashion store.

If something goes wrong

You are signed in but /admin says forbidden.
Your account is probably still a customer. Ask the agent to run the one-time promote script for your email against shop_local, then sign out and sign in again so the new role is read.
Image upload fails in the admin form.
Check the Cloudinary names in .env.local match .env.example, then ask the agent: "Show the upload error and check that the signature is created on the server for the right folder." Do not paste the keys into the chat.
A new product does not appear in the shop.
It is probably still a draft. Publish it, then confirm the shop pages only read published products and are not showing cached older data.
The form saves but prices look wrong, such as 100 times too high or low.
This is the whole-number money rule. Ask the agent to add a test that typing 19.99 stores 1999, and to show the conversion in one shared function used by both the form and the shop.

More in Back office

This is part of the series Build a secure, no-cost e-commerce website with AI agents. See all 24 episodes, the tools used and answers to common questions on the series page.

We use optional cookies for analytics and referral tracking. Learn more about our privacy policy