After a wedding, a conference or a marathon, the photographer uploads three thousand photos and every guest asks the same question: which ones am I in? Scrolling through a gallery that size is miserable. Uploading one selfie and getting back only your photos is a feature people actually remember.
This tutorial builds that feature with Amazon Rekognition's face collections, S3 and a small Node.js service. By the end you will have three pieces: indexing every face in a gallery, searching that index with a selfie, and the cost and privacy decisions you need to make before shipping it.
You will need an AWS account, Node.js 18 or later, and the AWS SDK for JavaScript v3.
npm install @aws-sdk/client-rekognition @aws-sdk/client-s3 How Rekognition face search works
Rekognition does not store your photos. When you index an image, it detects each face, turns it into a face vector, and stores that vector in a collection along with an id you choose. Later, you send a new image and ask, "which stored faces match this one?" It returns matching face ids with a similarity score.
So the design is simple:
- One collection per event.
- Index every gallery photo into it, tagging each face with the photo's key.
- Search the collection with the guest's selfie and return the photo keys that matched.
Step 1: Create a collection per event
import { RekognitionClient, CreateCollectionCommand, } from "@aws-sdk/client-rekognition"; const rek = new RekognitionClient({ region: "ap-south-1" }); export async function createEventCollection(eventId) { await rek.send(new CreateCollectionCommand({ CollectionId: `event-${eventId}`, })); } A collection per event keeps searches fast and accurate, because a guest's selfie is only compared against faces from that event. It also makes deletion clean: when the event's retention period ends, you delete one collection.
Step 2: Index the gallery
Photos live in S3, so Rekognition can read them directly without you downloading anything.
import { IndexFacesCommand } from "@aws-sdk/client-rekognition"; export async function indexPhoto(eventId, bucket, key) { const res = await rek.send(new IndexFacesCommand({ CollectionId: `event-${eventId}`, Image: { S3Object: { Bucket: bucket, Name: key } }, ExternalImageId: key.replace(/[^a-zA-Z0-9_.\-:]/g, "_"), MaxFaces: 15, QualityFilter: "AUTO", DetectionAttributes: [], })); return res.FaceRecords.length; } A few choices here matter:
-
ExternalImageIdis how you get from a matched face back to a photo. It only accepts a restricted character set, so sanitize the S3 key, or store a short id and keep a lookup table in your database. -
MaxFacescaps how many faces are indexed per photo. Group shots can contain dozens of tiny background faces that are useless for search and cost money to store. Rekognition keeps the largest faces first. -
QualityFilter: "AUTO"skips faces that are too blurry, dark or small to match reliably. Leave it on.
For a gallery of thousands of photos, do not index them in a loop inside a web request. Trigger indexing from an S3 upload event (S3 to SQS to a worker, or straight to Lambda), and keep concurrency modest to stay inside your account's Rekognition transactions-per-second limit.
Step 3: Search with a selfie
The guest's selfie never needs to touch S3. Send the bytes directly:
import { SearchFacesByImageCommand } from "@aws-sdk/client-rekognition"; export async function findMyPhotos(eventId, selfieBuffer) { const res = await rek.send(new SearchFacesByImageCommand({ CollectionId: `event-${eventId}`, Image: { Bytes: selfieBuffer }, FaceMatchThreshold: 90, MaxFaces: 500, })); const keys = new Set( res.FaceMatches.map((m) => m.Face.ExternalImageId) ); return [...keys]; } SearchFacesByImage finds the largest face in the selfie and compares it against the collection. One person can appear in many photos, so expect many matches and de-duplicate by photo.
Choosing the threshold. FaceMatchThreshold is the minimum similarity to return. At 80 you will catch more photos but start showing guests their cousin. At 95 you will miss profile shots and photos taken in poor light. For event galleries, 90 is a good starting point. Tune it against a real event before launch: take ten volunteers, count the photos each one should get, and compare.
Step 4: Handle the edge cases users will hit on day one
- No face in the selfie. Rekognition throws an
InvalidParameterException. Catch it and ask for a clearer, front-facing photo instead of returning an empty gallery. - Several people in the selfie. Only the largest face is searched. Say so in the upload screen.
- Sunglasses, masks, heavy makeup. Matching drops sharply. Telling guests "best results with a clear, front-facing selfie" saves support messages.
- Children's faces change fast and match less reliably across ages, which matters for school events.
Step 5: Cost
Rekognition charges per image processed for IndexFaces and SearchFacesByImage, plus a small monthly fee per thousand face vectors stored. Check the current price for your region, then estimate with real numbers:
- A 3,000-photo event costs 3,000 index calls once.
- Each guest search costs 1 call. 400 guests searching twice is 800 calls.
- Storage is charged on the number of faces stored, which is why
MaxFacesand the quality filter keep the bill down.
The biggest avoidable cost is re-indexing. Store which photos you have indexed so a retry or a redeploy does not process the whole gallery again.
Step 6: Privacy is part of the feature
Face vectors are biometric data, and in many places, including under India's DPDP Act and the EU's GDPR, that comes with real obligations. Build these in from the start:
- Consent. Tell guests clearly that their selfie is used only to find their photos, and get an explicit yes before upload.
- Do not keep selfies. Search with the bytes and discard them. Never index a guest's selfie into the collection.
- Retention. Delete the event's collection when the gallery expires:
import { DeleteCollectionCommand } from "@aws-sdk/client-rekognition"; await rek.send(new DeleteCollectionCommand({ CollectionId: `event-${eventId}`, })); - Opt-out. Let a guest remove their face from the index. Search with their selfie, then call
DeleteFacesCommandwith the matched face ids.
What you have built
An indexing pipeline that turns a gallery into a searchable face collection, a single endpoint that turns a selfie into a personal gallery, and the threshold, cost and privacy decisions that make it safe to ship. The whole feature is under 100 lines of application code. Most of the work is in the decisions around it, which is exactly where it should be.


