Scrypath is the runtime surface for schema → sync → search: you declare how records map into a Meilisearch index, enqueue or inline sync work, then query through the same Ecto-native boundaries your Phoenix or contexts already own.
Read next
- guides/golden-path.md — linear first hour from dependencies through a working
Scrypath.search/3. - guides/sync-modes-and-visibility.md — canonical sync modes (
:inline,:oban,:manual), eventual consistency, and operator lifecycle language. - guides/overview.md — table of contents for every published guide.
- guides/common-mistakes.md — evidence-led pitfalls when search and sync feel inconsistent.
Entry points
sync_record/3(and batch variants) — write path and mode semantics; start from guides/sync-modes-and-visibility.md.search/3— hydrated search on one schema; follow guides/golden-path.md for the first working call.search_many/2— federated multi-schema search; composition rules live in guides/golden-path.md and guides/multi-index-search.md.
Use use Scrypath on your Ecto schema; declaration grammar and settings live on
Scrypath.Schema — read that module instead of duplicating option tables here.
Reflection helpers
The initial public reflection surface is intentionally small:
schema_config/1schema_fields/1schema_settings/1schema_faceting/1document_source/1document_id_field/1
These functions keep reflection under Scrypath.* modules instead of generating
schema-specific runtime verbs.
Examples
iex> config = Scrypath.schema_config(SearchablePost)
iex> config.fields
[:title, :body]See also search_within_facet/4 in guides/faceted-search-with-phoenix-liveview.md
for searching inside a facet bucket alongside filter: / facet_filter: composition.
Summary
Functions
Upserts or deletes search documents through the configured backend.
Upserts or deletes search documents through the configured backend.
Upserts or deletes search documents through the configured backend.
Returns failed or retrying sync work rows for a schema.
Read-only index contract drift report for a searchable schema.
Report-first reconciliation for a schema (sync visibility, failed work, drift signals, and suggested recovery actions).
Returns normalized faceting: options for the schema, or [] when faceting is disabled.
Primary hydrated search entry: validates options, resolves runtime config, and
returns {:ok, search_result} or tagged {:error, _} failures from the
configured backend. Work is wrapped in the public search telemetry span
[:scrypath, :search] with stable, low-cardinality metadata.
Like search/3, but returns the same hydrated search result payload or raises
Scrypath.Search.Error when the non-bang API would return {:error, _}.
Full pipeline: see guides/per-query-tuning-pipeline.md.
Like search_many/2, but returns %Scrypath.MultiSearchResult{} or raises
Scrypath.Search.Error when the non-bang API would return {:error, _}.
Full-text search scoped to a single facet bucket, AND-combined with any other
filter:, facet_filter:, sort, and pagination options.
Upserts or deletes search documents through the configured backend.
Upserts or deletes search documents through the configured backend.
Functions
Upserts or deletes search documents through the configured backend.
On success, {:ok, map} includes at least :mode (for example :inline, :oban, or :manual)
and :status:
:status:accepted— work was queued or accepted by the backend or queue layer; documents may not be queryable yet. This is normal for:manual,:oban, and sometimes:inlinewhen no Meilisearch task wait applies. See guides/sync-modes-and-visibility.md.:status:completed— the:inlinepath finished, including waiting for a terminal Meilisearch task whensync_mode: :inlineand the backend returned a task handle.
Pitfalls when the database write “succeeded” but search lags: guides/common-mistakes.md.
Tagged {:error, reason} tuples may include {:timeout, _}, {:task_failed, _},
{:invalid_options, _, _}, and other operational heads. For user-facing copy,
normalize them through your own application boundary instead of depending on internal helpers.
Upserts or deletes search documents through the configured backend.
On success, {:ok, map} includes at least :mode (for example :inline, :oban, or :manual)
and :status:
:status:accepted— work was queued or accepted by the backend or queue layer; documents may not be queryable yet. This is normal for:manual,:oban, and sometimes:inlinewhen no Meilisearch task wait applies. See guides/sync-modes-and-visibility.md.:status:completed— the:inlinepath finished, including waiting for a terminal Meilisearch task whensync_mode: :inlineand the backend returned a task handle.
Pitfalls when the database write “succeeded” but search lags: guides/common-mistakes.md.
Tagged {:error, reason} tuples may include {:timeout, _}, {:task_failed, _},
{:invalid_options, _, _}, and other operational heads. For user-facing copy,
normalize them through your own application boundary instead of depending on internal helpers.
Upserts or deletes search documents through the configured backend.
On success, {:ok, map} includes at least :mode (for example :inline, :oban, or :manual)
and :status:
:status:accepted— work was queued or accepted by the backend or queue layer; documents may not be queryable yet. This is normal for:manual,:oban, and sometimes:inlinewhen no Meilisearch task wait applies. See guides/sync-modes-and-visibility.md.:status:completed— the:inlinepath finished, including waiting for a terminal Meilisearch task whensync_mode: :inlineand the backend returned a task handle.
Pitfalls when the database write “succeeded” but search lags: guides/common-mistakes.md.
Tagged {:error, reason} tuples may include {:timeout, _}, {:task_failed, _},
{:invalid_options, _, _}, and other operational heads. For user-facing copy,
normalize them through your own application boundary instead of depending on internal helpers.
@spec failed_sync_work( module(), keyword() ) :: {:ok, [Scrypath.Operator.FailedWork.t()]} | {:ok, Scrypath.Operator.FailedSyncWorkInspection.t()} | {:error, term()}
Returns failed or retrying sync work rows for a schema.
Reason class rollups
Pass reason_class_counts: true in operator options (alongside runtime
config) to receive {:ok, %Scrypath.Operator.FailedSyncWorkInspection{}}
with entries (the row list) and counts — a
%Scrypath.Operator.ReasonClassCounts{} with dense per-class frequencies.
The default remains {:ok, [FailedWork.t()]} when this option is omitted.
@spec index_contract_drift( module(), keyword() ) :: {:ok, Scrypath.Operator.IndexContractDrift.Report.t()} | {:error, term()}
Read-only index contract drift report for a searchable schema.
Compares declared schema metadata and resolved settings against a single live
Meilisearch GET /indexes/{uid}/settings snapshot. This is a report-first
operator surface: it does not enqueue work, mutate indexes, or imply recovery
actions. It is not the same signal family as reconcile_sync/2's
drift_signals, which reflects sync queue and reindex posture.
See include_index_contract_drift: true on reconcile_sync/2 when you want the
same report attached to a reconcile tuple (adds one get_settings read).
@spec reconcile_sync( module(), keyword() ) :: {:ok, Scrypath.Operator.Reconcile.t()} | {:error, term()} | {:ok, map()}
Report-first reconciliation for a schema (sync visibility, failed work, drift signals, and suggested recovery actions).
Index contract drift
Pass include_index_contract_drift: true alongside runtime options to
attach a read-only %Scrypath.Operator.IndexContractDrift.Report{} on
index_contract_drift. This performs an extra Meilisearch get_settings
read using the same builder as index_contract_drift/2. Omit the flag (default)
to avoid that latency and live dependency.
@spec retry_sync_work( Scrypath.Operator.FailedWork.t() | Scrypath.Operator.RecoveryAction.t(), keyword() ) :: {:ok, map()} | {:error, term()}
Returns normalized faceting: options for the schema, or [] when faceting is disabled.
Shape when enabled is a keyword list with :attributes, :max_values_per_facet, and
:sort_facet_values_by.
Primary hydrated search entry: validates options, resolves runtime config, and
returns {:ok, search_result} or tagged {:error, _} failures from the
configured backend. Work is wrapped in the public search telemetry span
[:scrypath, :search] with stable, low-cardinality metadata.
Full pipeline: see guides/per-query-tuning-pipeline.md.
Optional :per_query is an allowlisted keyword for Plane B Meilisearch search
parameters on the v1.9 slice (for example ranking_score_threshold,
show_ranking_score, and show_ranking_score_details); semantics and telemetry
expectations are defined in
guides/per-query-tuning-pipeline.md.
Errors vs raises
ArgumentError— some invalid shapes are rejected synchronously before backend dispatch.{:error, reason}— operational failures, including backend errors and tuples such as{:transport_failed, _}, for callers that want to branch.
search!/3 raises Scrypath.Search.Error with the same reason instead of returning
{:error, _}.
Like search/3, but returns the same hydrated search result payload or raises
Scrypath.Search.Error when the non-bang API would return {:error, _}.
Full pipeline: see guides/per-query-tuning-pipeline.md.
search_many/2 merge rules for shared vs per-entry options, :all expansion,
and federation payloads remain canonical in guides/multi-index-search.md.
:per_query may appear on shared keywords and on each per-entry tuple; when
both sides supply it, inner keys shallow-merge with entry bias — see
guides/per-query-tuning-pipeline.md.
Federated search across multiple schemas.
Entries mirror search/3 tuples; optional federation_weight: tunes
Meilisearch merge ordering for that row and requires a backend that implements
search_many/2. Invalid weights use {:invalid_options, {:federation_weight, _}};
backends without native multi-search return
{:invalid_options, {:federation_merge_requires_native_search_many, %{backend: _}}}.
Successful responses are {:ok, multi_search_result} with per-schema search
results. When the backend returns a flat federated hits list,
merge_hit_order may be populated so callers can inspect merged ordering;
otherwise merge_hit_order is nil.
See also guides/multi-index-search.md § Federation weights.
Errors vs raises
{:error, reason}— preflight and transport failures you should branch on ({:invalid_options, _},{:validation_failed, _, _},{:transport_failed, _}, …).search_many!/2raisesScrypath.Search.Errorwith the samereasoninstead of returning{:error, _}.
Like search_many/2, but returns %Scrypath.MultiSearchResult{} or raises
Scrypath.Search.Error when the non-bang API would return {:error, _}.
@spec search_within_facet(module(), String.t(), {atom(), term() | list()}, keyword()) :: {:ok, term()} | {:error, term()}
Full-text search scoped to a single facet bucket, AND-combined with any other
filter:, facet_filter:, sort, and pagination options.
facet_bucket is {facet_attribute, value} where value is either a scalar
(one bucket) or a list interpreted as OR within that attribute, matching
facet_filter: encoding. Passing the same attribute again under facet_filter:
is rejected with ArgumentError — use search/3 or keep only one source of truth.
Errors vs raises
ArgumentError— structural misuse of the facet bucket or duplicate facet locks.{:error, reason}— same operational{:error, _}family assearch/3.
search_within_facet!/4 raises Scrypath.Search.Error when the non-bang call would
return {:error, _}.
Upserts or deletes search documents through the configured backend.
On success, {:ok, map} includes at least :mode (for example :inline, :oban, or :manual)
and :status:
:status:accepted— work was queued or accepted by the backend or queue layer; documents may not be queryable yet. This is normal for:manual,:oban, and sometimes:inlinewhen no Meilisearch task wait applies. See guides/sync-modes-and-visibility.md.:status:completed— the:inlinepath finished, including waiting for a terminal Meilisearch task whensync_mode: :inlineand the backend returned a task handle.
Pitfalls when the database write “succeeded” but search lags: guides/common-mistakes.md.
Tagged {:error, reason} tuples may include {:timeout, _}, {:task_failed, _},
{:invalid_options, _, _}, and other operational heads. For user-facing copy,
normalize them through your own application boundary instead of depending on internal helpers.
Upserts or deletes search documents through the configured backend.
On success, {:ok, map} includes at least :mode (for example :inline, :oban, or :manual)
and :status:
:status:accepted— work was queued or accepted by the backend or queue layer; documents may not be queryable yet. This is normal for:manual,:oban, and sometimes:inlinewhen no Meilisearch task wait applies. See guides/sync-modes-and-visibility.md.:status:completed— the:inlinepath finished, including waiting for a terminal Meilisearch task whensync_mode: :inlineand the backend returned a task handle.
Pitfalls when the database write “succeeded” but search lags: guides/common-mistakes.md.
Tagged {:error, reason} tuples may include {:timeout, _}, {:task_failed, _},
{:invalid_options, _, _}, and other operational heads. For user-facing copy,
normalize them through your own application boundary instead of depending on internal helpers.
@spec sync_status( module(), keyword() ) :: {:ok, Scrypath.Operator.Status.t()} | {:error, term()}