Documentation Index
Fetch the complete documentation index at: https://docs.deepface.dev/llms.txt
Use this file to discover all available pages before exploring further.
Customer-owned face search with Supabase pgvector
Use this pattern when you want scalable face search, but you do not want
deepface.dev to host your face database.
The flow is:
- Send an image to
POST /represent.
- Store the returned embedding in your own Supabase
pgvector table.
- For a new query image, call
POST /represent again.
- Search your own Supabase table for the closest embeddings.
deepface.dev still processes the images and embeddings you submit so it can
return API results. The difference is that your enrolled face database, person
IDs, labels, and search index stay in your Supabase project.
How this relates to DeepFace register and search
Open-Source DeepFace now includes database-backed register, search, and
build_index functions for stateless face recognition pipelines. In that model,
register stores embeddings in a backend database and search queries that
database.
deepface.dev does not expose hosted /register or /search endpoints today.
Instead, use POST /represent for the compute step and keep registration,
storage, indexing, and search in your own database.
References:
Create the Supabase table
This example uses Facenet, which returns 128-dimensional embeddings. If you
choose another model, confirm its embedding length and change vector(128) to
match.
create extension if not exists vector;
create table if not exists public.face_embeddings (
id uuid primary key default gen_random_uuid(),
person_id text not null,
label text,
embedding vector(128) not null,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now()
);
alter table public.face_embeddings enable row level security;
create index if not exists face_embeddings_embedding_hnsw_idx
on public.face_embeddings
using hnsw (embedding vector_cosine_ops);
For production, add RLS policies that match your app’s account model. If you use
a service-role key in a backend job, keep it server-side only and never expose it
in browser code.
Query nearest faces
Use cosine distance (<=>) to match deepface.dev’s default cosine comparison
metric. Smaller distances are closer matches.
Create a database function so Postgres can order by vector distance directly:
create or replace function public.match_faces(
query_embedding vector(128),
match_count int default 5
)
returns table (
id uuid,
person_id text,
label text,
metadata jsonb,
distance float
)
language sql
stable
as $$
select
face_embeddings.id,
face_embeddings.person_id,
face_embeddings.label,
face_embeddings.metadata,
face_embeddings.embedding <=> query_embedding as distance
from public.face_embeddings
order by face_embeddings.embedding <=> query_embedding
limit match_count;
$$;
JavaScript example
Install the Supabase client in your backend project:
npm install @supabase/supabase-js
Then generate, store, and search embeddings from a server-side script or API
route:
import { createClient } from "@supabase/supabase-js";
const deepfaceApiBaseUrl =
process.env.DEEPFACE_API_BASE_URL || "https://api.deepface.dev";
const deepfaceApiKey = process.env.DEEPFACE_API_KEY;
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!deepfaceApiKey || !supabaseUrl || !supabaseServiceRoleKey) {
throw new Error("Missing DEEPFACE_API_KEY, SUPABASE_URL, or SUPABASE_SERVICE_ROLE_KEY.");
}
const supabase = createClient(supabaseUrl, supabaseServiceRoleKey, {
auth: { persistSession: false },
});
async function represent(imageBase64) {
const response = await fetch(`${deepfaceApiBaseUrl}/represent`, {
method: "POST",
headers: {
Authorization: `Bearer ${deepfaceApiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model_name: "Facenet",
img: imageBase64,
}),
});
if (!response.ok) {
throw new Error(`deepface.dev /represent failed: ${response.status}`);
}
const result = await response.json();
const embedding = Array.isArray(result) ? result[0]?.embedding : result.embedding;
if (!Array.isArray(embedding)) {
throw new Error("No embedding returned by deepface.dev.");
}
return embedding;
}
async function registerFace({ personId, label, imageBase64, metadata = {} }) {
const embedding = await represent(imageBase64);
const { error } = await supabase.from("face_embeddings").insert({
person_id: personId,
label,
embedding,
metadata,
});
if (error) {
throw error;
}
}
async function searchFace({ imageBase64, limit = 5 }) {
const embedding = await represent(imageBase64);
const { data, error } = await supabase.rpc("match_faces", {
query_embedding: embedding,
match_count: limit,
});
if (error) {
throw error;
}
return data;
}
When to use this pattern
Use customer-owned search when you need:
- face search across many enrolled identities;
- control over where biometric data and metadata are stored;
- your own RLS, audit, retention, and deletion rules;
- database-level ANN search as your dataset grows.
For small request-time comparisons, POST /compare can still compare one image
or vector against vectors you include in the request. For larger datasets, keep
the database search in Supabase and use deepface.dev only for embedding
generation.