Auto Generate Conference Tags with Cloudinary

Banner for a MediaJam post

Ekene Eze

Conference organizers often need to issue conference attendance tags to users who sign up to attend their conferences. This is often a daunting task requiring them to manually create and customize these tags for the hundreds and possibly thousands of attendees.

What if we can automate the process and generate a visually appealing conference tag upon registration for every attendee using Cloudinary and Next.js? That is what we'll discuss in this post.

First, a brief about both technologies:

Cloudinary is a modern asset management tool that helps you create, manage and deliver high-quality digital experiences to users. If you’d like to follow along with this post, you’ll need to create a free Cloudinary account.

Next.js is a React-based frontend framework that supports server-side rendering and static site generation. We’ll be building the tag generation project with it.

If you’re eager to get a quick look at the finished product, it is available here on Codesanbox

and also hosted on Github.

Prerequisites

Before we proceed, let’s make sure you've correctly set up and ready to build along. You will need the following:

  • Some experience with JavaScript and Next.js.
  • Node.js installed for local development.
  • Some experience with Cloudinary.
  • Some experience styling with Bootstrap

Setup and Installations

Run the following command in the terminal to create a new Next.js application named conference-tags:

1npx create-next-app conference-tags

This will create a conference-tags application for you. Next, let’s change directory into the conference-tags project we just created:

1cd conference-tags # to navigate into the project directory

Next, we'll install the following npm packages:

Run the command below to install the packages above:

1npm install @cloudinary/react @cloudinary/url-gen

Setting up Cloudinary console

Since we want to have a base image hosted on Cloudinary that we can fetch and add transformations to, we’ll first need to upload it to Cloudinary. You can access the base image I uploaded here and reuse it for your own demo if you’re building along.

For every user that registers for our conference, we want to:

  1. Overlay their details on the base image we currently have on Cloudinary
  2. Upload the new image (now containing the user’s data) to Cloudinary
  3. Finally display the image with a link for the user to download their tags.

To do that, let’s first set up a Cloudinary upload preset. Log into your Cloudinary account and click on Settings > Upload.

Now, scroll down to upload presets and click on Add upload presets. Then:

  • Provide an upload preset name.
  • Set signing mode to unsigned, and
  • Set the desired folder to hold the image uploads.

Now that we have the initial Cloudinary settings out of the way, we can start building our tag generator application. First, run the development server with npm run dev to visualize the changes in the browser as we build the project.

Next, open the project folder in your preferred code editor and create a components folder in the root of the project. Inside it, create a new Tag.js file and update it with this snippet:

1// components/Tag.js
2import { Cloudinary, Transformation } from "@cloudinary/url-gen";
3import { AdvancedImage } from "@cloudinary/react";
4import { source } from "@cloudinary/url-gen/actions/overlay";
5import { image, text } from "@cloudinary/url-gen/qualifiers/source";
6import { scale } from "@cloudinary/url-gen/actions/resize";
7import { Position } from "@cloudinary/url-gen/qualifiers";
8import { compass } from "@cloudinary/url-gen/qualifiers/gravity";
9import { TextStyle } from "@cloudinary/url-gen/qualifiers/textStyle";
10import { byRadius } from "@cloudinary/url-gen/actions/roundCorners";
11import { brightness } from "@cloudinary/url-gen/actions/adjust";
12
13export default function Tag({ formData }) {
14 // component code here
15}

The Tag component above will represent every tag we generate for every user when they sign up to attend this conference. What we’ve done so far is import all the Cloudinary packages we’ll need to achieve the desired transformation.

Next, let’s create a new Cloudinary instance with our cloud_name to establish a connection between this project and our Cloudinary account:

1// imports here
2export default function Tag({ formData }) {
3 const cld = new Cloudinary({
4 cloud: {
5 cloudName: process.env.NEXT_PUBLIC_CLOUD_NAME,
6 },
7 });
8}

With that, we can now define a baseImage variable which can directly access our Cloudinary account to get the already existing base image:

1// imports here
2export default function Tag({ formData }) {
3 // init cloudinary
4 let baseImage = cld.image(`base-tag`);
5}

With that, we can now apply transformations on the base image to overlay the user’s details on it. Before we proceed, here’s something to note:

The Tag component will be rendered in the index.js file where we’ll accept the user data via a registration form and then pass that data to the Tag component as props. The props will give us access to the name, role, and avatar of the users which we can then overlay the base image like so:

1// src/components/Tag
2export default function Tag({ formData }) {
3 const { name, role, avatar } = formData;
4 baseImage.overlay(
5 source(
6 image(`${avatar}`).transformation(
7 new Transformation()
8 .resize(scale().width(400).height(400))
9 .roundCorners(byRadius(230))
10 .adjust(brightness(5))
11 )
12 ).position(new Position().gravity(compass("center")).offsetY(-50))
13 );
14 baseImage.overlay(
15 source(
16 text(`${name}`, new TextStyle("Nunito", 65)).textColor("white")
17 ).position(new Position().gravity(compass("center")).offsetY(210))
18 );
19 baseImage.overlay(
20 source(
21 text(`${role}`, new TextStyle("Nunito", 70)).textColor("purple")
22 ).position(new Position().gravity(compass("center")).offsetY(350))
23 );
24 return();
25}

Finally, we render the transformed base image for the user to see via the Cloudinary <AdvancedImage /> component:

1// src/components/Tag
2export default function Tag({ formData }) {
3// other component code
4return (
5 <div>
6 <AdvancedImage cldImg={baseImage} />
7 </div>
8 );
9}

With that, we are done with the Tag component. You can find the complete Tag.js component code embeded in this Github gist.

Moving on, let’s update the index.js file to create the registration form and collect the user data when the form is submitted.

1// src/pages/index
2import Head from "next/head";
3import Image from "next/image";
4import styles from "../styles/Home.module.css";
5import { useState } from "react";
6import { Form, Button } from "react-bootstrap";
7
8const initialState = { name: "", avatar: "", role: "" };
9export default function Home() {
10 const [formData, setFormData] = useState(initialState);
11 const [disableInput, setDisableInput] = useState(false);
12 const [imageLoaded, setImageLoaded] = useState(true);
13 // more component code here
14}

What we’ve done in the Home page above is import Next.js and Bootstrap components that we’ll be using shortly to build out the form. We also initialized some relevant variables using the useState hook for the same purpose. In the form, we want to have 3 fields for name, role, and avatar like so:

1//src/pages/index
2//import statements here
3
4const initialState = { name: "", avatar: "", role: "" };
5export default function Home() {
6return (
7 <div>
8 <main>
9 <h1>
10 Register to attend iJam Conf'22!
11 </h1>
12 <div>
13 <div>
14 <Form >
15 <Form.Group>
16 <Form.Label>Name</Form.Label>
17 <Form.Control
18 type="text"
19 name="name"
20 required
21 disabled={disableInput}
22 onChange={handleChange}
23 value={formData.name ?? ""}
24 />
25 </Form.Group>
26 <Form.Group controlId="formFile">
27 <Form.Label>Avatar</Form.Label>
28 <Form.Control
29 type="file"
30 accept="image/*"
31 onChange={(e) => handleImage(e)}
32 disabled={disableInput}
33 />
34 </Form.Group>
35 <h4>Choose Role</h4>
36 <div>
37 <Form.Check
38 type="radio"
39 label={`Speaker`}
40 value={`SPEAKER` ?? ""}
41 name="role"
42 onChange={handleChange}
43 />
44 <Form.Check
45 type="radio"
46 label={`Attendee`}
47 value={`ATTENDEE` ?? ""}
48 name="role"
49 onChange={handleChange}
50 />
51 </div>
52 <div>
53 <Button
54 onClick={(e) => {
55 e.preventDefault();
56 setShowTag(true);
57 setDisableInput(true);
58 setImageLoaded(true);
59 }}
60 type="submit"
61 variant="primary"
62 disabled={imageLoaded}
63 >
64 Generate Tag
65 </Button>
66 </div>
67 </Form>
68 </div>
69 </div>
70 </main>
71 </div>
72 );
73}

Here, we have a simple form with three fields to collect the users name, avatar and their roles respectively. At the moment, the code will error out since we are calling functions in it that we haven’t defined yet. The handleChange() and handleImage() functions — let’s define them below:

1//src/pages/index
2//import statements here
3
4const initialState = { name: "", avatar: "", role: "" };
5export default function Home() {
6 const [formData, setFormData] = useState(initialState);
7 const [disableInput, setDisableInput] = useState(false);
8 const [imageLoaded, setImageLoaded] = useState(true);
9
10 const handleChange = async (e) => {
11 e.preventDefault();
12 setFormData({ ...formData, [e.target.name]: e.target.value });
13 };
14
15 const handleImage = async (e) => {
16 const reader = new FileReader();
17 reader.onloadend = async () => {
18 setImageLoaded(true);
19 };
20 const files = e.target.files[0];
21 if (!files) return;
22 const data = new FormData();
23 data.append("file", files);
24 data.append("upload_preset", "conference_tags");
25 const res = await fetch(
26 "https://api.cloudinary.com/v1_1/kennyy/image/upload",
27 {
28 method: "POST",
29 body: data,
30 }
31 );
32 const file = await res.json();
33 setFormData({ ...formData, avatar: file.public_id });
34 setImageLoaded(false);
35 };
36 // return() function here
37}

The handleChange() function updates the value of our formData object in state with the value of the input fields as the user fills the form.

When the user clicks on the field to choose an image, we call the handleImage() function to access our filesystem, select an image, upload the selected image to Cloudinary and update our formData object with the publicID of the uploaded image. At this point, if we check back on the browser, we should see the registration form:

With this, we have now accounted for all the fields in our form. We have access to the name of the user, the role they've selected and their avatar. As a result, we can render our Tag component and pass the user data to it. The component will accept the details as a prop and then overlay it on our base image and finally generate the conference tag for the user:

1{showTag && (
2 <Tags
3 formData={formData}
4 />
5)}

And with this, we are now successfully passing the user data to our Tag component. The complete index.js file is available in this gist for your consumption. And now, the moment of truth. Let's take this project for a spin!

Summary

In this post, we've leveraged Cloudinary's image transformation capabilities to automatically generate conference tags on the fly for all users registering for our iJam conference. If you'd like to extend this project, you're welcome to fork this repo and make changes as you see fit.

Ekene Eze

Director of DX at Plasmic

I work at Plasmic as the Director of Developer Experience and I love executing projects that help other software engineers. Passionate about sharing with the community, I often write, present workshops, create courses, and speak at conferences about web development concepts and best practices.