# Getting Started
Scrypath gives Phoenix and Ecto teams an explicit path for declaring searchable schemas, syncing search documents, and querying through one context-owned boundary.
## What You Set Up
Start with three pieces:
1. A schema that declares search metadata with `use Scrypath`
2. A context that owns repo persistence plus `Scrypath.*` orchestration
3. A backend configuration that keeps sync mode explicit, plus Oban only if you want queued sync
## Declare A Searchable Schema
```elixir
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, Ecto.Enum, values: [:draft, :published]
timestamps()
end
end
```
`use Scrypath` stays metadata-only. Runtime orchestration still lives in your context modules.
Scrypath also owns its internal transport dependency, so the base install path stays focused on adding `:scrypath`.
## Put Search And Sync In The Context
```elixir
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
end
```
That keeps reads, writes, sync decisions, and failure handling in one application boundary instead of spreading them across controllers or LiveView callbacks.
## Keep Web Modules Thin
Controllers and LiveView modules call the same context boundary:
```elixir
defmodule MyAppWeb.PostController do
use MyAppWeb, :controller
alias MyApp.Content
def index(conn, params) do
{:ok, result} =
Content.search_posts(Map.get(params, "q", ""),
filter: [status: "published"]
)
render(conn, :index, posts: result.records, search: result)
end
end
```
```elixir
defmodule MyAppWeb.PostLive do
use MyAppWeb, :live_view
alias MyApp.Content
def handle_params(%{"q" => query}, _uri, socket) do
{:ok, result} = Content.search_posts(query, preload: [:author])
{:noreply, assign(socket, posts: result.records, search: result, query: query)}
end
end
```
## Choose Sync Mode Deliberately
- `:inline` waits for terminal backend success before returning
- `:manual` returns accepted backend work immediately for imports and operator-driven flows
- `:oban` returns durable enqueue acceptance only and is an optional production path when you want queued sync
Accepted work is not the same thing as search visibility. Pick the mode that matches your consistency and operational constraints.
## Continue
- Read [Phoenix Walkthrough](phoenix-walkthrough.html) for the first end-to-end path
- Read [Phoenix Contexts](phoenix-contexts.html) for the recommended boundary shape
- Read [Sync Modes And Visibility](sync-modes-and-visibility.html) before choosing `:manual` or `:oban`