If you prefer the conceptual overview first, read Getting Started. This guide is a linear checklist from dependency install through a working Scrypath.search/3 using sync_mode: :inline only. Queueing and manual sync are intentionally deferred to Sync Modes and Visibility (guides/sync-modes-and-visibility.md).
When you need Scrypath.search_many/2, federation weights, or :all expansion across several schemas, read next: Multi-index search.
Goal
By the end of this path you will have Meilisearch running locally, minimal application configuration pointing Scrypath at it, a searchable Ecto schema, and one context-owned function pair: Scrypath.sync_record/3 after a successful repo write and Scrypath.search/3 returning hydrated records. The first hour stays inline so success in your function means the backend finished the write—not that database and search are a single atomic transaction.
Dependencies
Add Scrypath to your app’s mix.exs:
def deps do
[
{:scrypath, "~> 0.3"}
]
endRun mix deps.get. The minor-flexible range matches published 0.3.x releases; bump the constraint when you intentionally adopt a new minor.
Bring up Meilisearch
Scrypath’s v1 story assumes Meilisearch. For a stack aligned with this repository’s CI and example app, use Docker Compose from examples/phoenix_meilisearch/ (image getmeili/meilisearch:v1.15, default URL http://127.0.0.1:7700):
cd path/to/examples/phoenix_meilisearch
docker compose up -d
Set SCRYPATH_MEILISEARCH_URL to your Meilisearch base URL if it is not the default (for example export SCRYPATH_MEILISEARCH_URL=http://127.0.0.1:7700).
For a full Phoenix + Postgres smoke (fixtures, scripted test), follow examples/phoenix_meilisearch/README.md (repository path examples/phoenix_meilisearch/README.md).
For where Meilisearch fits in production (networking, keys, backups vs rebuilding from Postgres), read Meilisearch operations for Scrypath adopters.
Application config
Point your otp_app at Meilisearch in config/runtime.exs or config/dev.exs (pick the file your team uses for local secrets):
import Config
config :my_app, :scrypath,
meilisearch_url: System.get_env("SCRYPATH_MEILISEARCH_URL") || "http://127.0.0.1:7700"Wire that into Scrypath’s backend configuration wherever you centralize runtime options (see Getting Started for the shape: backend module + explicit sync_mode at call sites). Use placeholders for URLs in docs you commit; never embed real API keys.
Schema
Declare search metadata on the Ecto schema with use Scrypath:
defmodule MyApp.Blog.Post do
use Ecto.Schema
use Scrypath,
fields: [:title, :body],
filterable: [:status],
sortable: [:inserted_at]
schema "posts" do
field :title, :string
field :body, :string
field :status, :string
timestamps()
end
enduse Scrypath is metadata-only; persistence and sync stay in your context.
In production you may still enforce allowed values with validate_inclusion/3 or Ecto.Enum on changesets; this guide keeps field :status, :string in the schema so Meilisearch filters stay string-shaped (for example status = "published") and match the example app.
Context: inline sync and search
Own both repo writes and Scrypath calls from one context module:
defmodule MyApp.Content do
alias MyApp.Blog.Post
alias MyApp.Repo
def search_posts(query, opts \\ []) do
Scrypath.search(Post, query,
Keyword.merge([backend: Scrypath.Meilisearch, repo: Repo], opts)
)
end
def publish_post(post, attrs) do
with {:ok, post} <- update_post(post, attrs),
{:ok, _sync} <-
Scrypath.sync_record(Post, post,
backend: Scrypath.Meilisearch,
sync_mode: :inline
) do
{:ok, post}
end
end
defp update_post(post, attrs) do
post
|> Post.changeset(attrs)
|> Repo.update()
end
endsync_mode: :inline means the call waits for terminal backend success before returning. It still does not make the database write and the search write atomic—operational honesty matters when you reason about failures.
Proof of search (IEx)
After you have migrated data (or inserted a row in dev), prove search from iex -S mix:
alias MyApp.Content
{:ok, result} = Content.search_posts("hello", [])
result.recordsIndex-time relevance tuning lives in Relevance tuning; request-time search options are specified in the Per-query tuning pipeline guide.
For controllers, JSON APIs, and LiveView that call the same context boundary, continue with Phoenix Walkthrough.
Integration smoke (Postgres + Meilisearch + Oban)
The golden path stays inline on purpose. To run the multi-container proof (inline + :oban integration tests, same Meilisearch image pin as CI), use the runnable example—do not duplicate env tables here:
- Runbook (from
examples/phoenix_meilisearch/— commands, env vars,./scripts/smoke.sh):examples/phoenix_meilisearch/README.md - CI: On pull requests and pushes to
main, GitHub Actions runs jobphoenix-example-integration, which starts Postgres 16 (postgres:16-alpine) and Meilisearch (getmeili/meilisearch:v1.15) as workflow services, setsSCRYPATH_EXAMPLE_INTEGRATION=1,PGPORT=5433, andSCRYPATH_MEILISEARCH_URL=http://127.0.0.1:7700, then runscd examples/phoenix_meilisearch && mix deps.get && mix test—the same consumer-shapedmix testpath the example README documents for local Compose. For job names, env vars, and how they map tomix verify.*tasks, see rootCONTRIBUTING.md.
Ecto without Phoenix (API-only)
You do not need MyAppWeb modules. The same context-owned pattern applies in an API-only app: call Scrypath.sync_record/3 after Repo.insert / Repo.update succeeds, and expose Scrypath.search/3 through functions you call from plugs or bounded contexts. Use examples/phoenix_meilisearch/README.md as the entry to the minimal consumer-shaped reference (the example is Phoenix, but the search boundary is still “context owns Scrypath”).
What is next
- Sync modes: When you outgrow inline sync for throughput or durability, read Sync Modes and Visibility (
guides/sync-modes-and-visibility.md) for:obanand:manual, operator lifecycle states, and recovery language. Production often uses:oban; the first hour above stays inline on purpose. - Oban: If you choose
:oban, add Oban as a dependency and follow its installation guides; Scrypath stays explicit about enqueue vs visibility—see the sync guide for the contract.