Skip to main content

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:
  1. Send an image to POST /represent.
  2. Store the returned embedding in your own Supabase pgvector table.
  3. For a new query image, call POST /represent again.
  4. 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. 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.