Skip to main content
Glama

Gemini Docs MCP Server

by M-Gonzalo
phx-docs.md1.72 MB
=== phx-docs/phoenix/js/index.html.md === ### phoenix `1.7.18` - [phoenix](index.html#phoenix) - [Channel ▸](index.html#channel) - Instance members - [#join](index.html#channeljoin) - [#onClose](index.html#channelonclose) - [#onError](index.html#channelonerror) - [#on](index.html#channelon) - [#off](index.html#channeloff) - [#push](index.html#channelpush) - [#leave](index.html#channelleave) - [#onMessage](index.html#channelonmessage) - [Push ▸](index.html#push) - Instance members - [#resend](index.html#pushresend) - [#send](index.html#pushsend) - [#receive](index.html#pushreceive) - [Presence ▸](index.html#presence) - Static members - [.syncState](index.html#presencesyncstate) - [.syncDiff](index.html#presencesyncdiff) - [.list](index.html#presencelist) - [Socket ▸](index.html#socket) - Instance members - [#getLongPollTransport](index.html#socketgetlongpolltransport) - [#replaceTransport](index.html#socketreplacetransport) - [#protocol](index.html#socketprotocol) - [#endPointURL](index.html#socketendpointurl) - [#disconnect](index.html#socketdisconnect) - [#connect](index.html#socketconnect) - [#log](index.html#socketlog) - [#hasLogger](index.html#sockethaslogger) - [#onOpen](index.html#socketonopen) - [#onClose](index.html#socketonclose) - [#onError](index.html#socketonerror) - [#onMessage](index.html#socketonmessage) - [#ping](index.html#socketping) - [#connectionState](index.html#socketconnectionstate) - [#isConnected](index.html#socketisconnected) - [#off](index.html#socketoff) - [#channel](index.html#socketchannel) - [#push](index.html#socketpush) - [#makeRef](index.html#socketmakeref) - [Timer ▸](index.html#timer) - Instance members - [#scheduleTimeout](index.html#timerscheduletimeout) [Need help reading this?](https://documentation.js.org/reading-documentation.html) ### phoenix Phoenix Channels JavaScript client ## Socket Connection A single connection is established to the server and channels are multiplexed over the connection. Connect to the server using the `Socket` class: The `Socket` constructor takes the mount point of the socket, the authentication params, as well as options that can be found in the Socket docs, such as configuring the `LongPoll` transport, and heartbeat. ## Channels Channels are isolated, concurrent processes on the server that subscribe to topics and broker events between the client and server. To join a channel, you must provide the topic, and channel params for authorization. Here's an example chat room example where `"new_msg"` events are listened for, messages are pushed to the server, and the channel is joined with ok/error/timeout matches: ## Joining Creating a channel with `socket.channel(topic, params)`, binds the params to `channel.params`, which are sent up on `channel.join()`. Subsequent rejoins will send up the modified params for updating authorization params, or passing up last\_message\_id information. Successful joins receive an "ok" status, while unsuccessful joins receive "error". With the default serializers and WebSocket transport, JSON text frames are used for pushing a JSON object literal. If an `ArrayBuffer` instance is provided, binary encoding will be used and the message will be sent with the binary opcode. *Note*: binary messages are only supported on the WebSocket transport. ## Duplicate Join Subscriptions While the client may join any number of topics on any number of channels, the client may only hold a single subscription for each unique topic at any given time. When attempting to create a duplicate subscription, the server will close the existing channel, log a warning, and spawn a new channel for the topic. The client will have their `channel.onClose` callbacks fired for the existing channel, and the new channel join will have its receive hooks processed as normal. ## Pushing Messages From the previous example, we can see that pushing messages to the server can be done with `channel.push(eventName, payload)` and we can optionally receive responses from the push. Additionally, we can use `receive("timeout", callback)` to abort waiting for our other `receive` hooks and take action after some period of waiting. The default timeout is 10000ms. ## Socket Hooks Lifecycle events of the multiplexed connection can be hooked into via `socket.onError()` and `socket.onClose()` events, ie: ## Channel Hooks For each joined channel, you can bind to `onError` and `onClose` events to monitor the channel lifecycle, ie: ### onError hooks `onError` hooks are invoked if the socket connection drops, or the channel crashes on the server. In either case, a channel rejoin is attempted automatically in an exponential backoff manner. ### onClose hooks `onClose` hooks are invoked only in two cases. 1) the channel explicitly closed on the server, or 2). The client explicitly closed, by calling `channel.leave()` ## Presence The `Presence` object provides features for syncing presence information from the server with the client and handling presences joining and leaving. ### Syncing state from the server To sync presence state from the server, first instantiate an object and pass your channel in to track lifecycle events: Next, use the `presence.onSync` callback to react to state changes from the server. For example, to render the list of users every time the list changes, you could write: ### Listing Presences `presence.list` is used to return a list of presence information based on the local state of metadata. By default, all presence metadata is returned, but a `listBy` function can be supplied to allow the client to select which metadata to use for a given presence. For example, you may have a user online from different devices with a metadata status of "online", but they have set themselves to "away" on another device. In this case, the app may choose to use the "away" status for what appears on the UI. The example below defines a `listBy` function which prioritizes the first metadata which was registered for each user. This could be the first tab they opened, or the first device they came online from: ### Handling individual presence join and leave events The `presence.onJoin` and `presence.onLeave` callbacks can be used to react to individual presences joining and leaving the app. For example: phoenix ### Channel new Channel(topic: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), params: ([Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object) | [function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)), socket: [Socket](index.html#socket)) Parameters topic `(string)` params `((Object | function))` socket `(Socket)` Instance Members [▸]() join(timeout) Join the channel join(timeout: integer): [Push](index.html#push) Parameters timeout `(integer = this.timeout)` Returns `Push`: [▸]() onClose(callback) Hook into channel close onClose(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` [▸]() onError(callback) Hook into channel errors onError(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` [▸]() on(event, callback) Subscribes on channel events Subscription returns a ref counter, which can be used later to unsubscribe the exact event listener on(event: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)): integer Parameters event `(string)` callback `(Function)` Returns `integer`: ref Example ``` const ref1 = channel.on("event", do_stuff) const ref2 = channel.on("event", do_other_stuff) channel.off("event", ref1) // Since unsubscription, do_stuff won't fire, // while do_other_stuff will keep firing on the "event" ``` [▸]() off(event, ref) Unsubscribes off of channel events Use the ref returned from a channel.on() to unsubscribe one handler, or pass nothing for the ref to unsubscribe all handlers for the given event. off(event: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), ref: integer) Parameters event `(string)` ref `(integer)` Example ``` // Unsubscribe the do_stuff handler const ref1 = channel.on("event", do_stuff) channel.off("event", ref1) // Unsubscribe all handlers from event channel.off("event") ``` [▸]() push(event, payload, timeout = this.timeout) Sends a message `event` to phoenix with the payload `payload`. Phoenix receives this in the `handle_in(event, payload, socket)` function. if phoenix replies or it times out (default 10000ms), then optionally the reply can be received. push(event: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), payload: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), timeout: [number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?): [Push](index.html#push) Parameters event `(string)` payload `(Object)` timeout `(number? = this.timeout)` Returns `Push`: Example ``` channel.push("event") .receive("ok", payload => console.log("phoenix replied:", payload)) .receive("error", err => console.log("phoenix errored", err)) .receive("timeout", () => console.log("timed out pushing")) ``` [▸]() leave(timeout) Leaves the channel Unsubscribes from server events, and instructs channel to terminate on server Triggers onClose() hooks To receive leave acknowledgements, use the `receive` hook to bind to the server ack, ie: leave(timeout: integer): [Push](index.html#push) Parameters timeout `(integer = this.timeout)` Returns `Push`: Example ``` channel.leave().receive("ok", () => alert("left!") ) ``` [▸]() onMessage(\_event, payload, \_ref, event, ref) Overridable message hook Receives all events for specialized message handling before dispatching to the channel callbacks. Must return the payload, modified or unmodified onMessage(\_event: any, payload: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), \_ref: any, event: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), ref: integer): [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object) Parameters \_event `(any)` payload `(Object)` \_ref `(any)` event `(string)` ref `(integer)` Returns `Object`: ### Push Initializes the Push new Push(channel: [Channel](index.html#channel), event: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), payload: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), timeout: [number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)) Parameters channel `(Channel)` The Channel event `(string)` The event, for example `"phx_join"` payload `(Object)` The payload, for example `{user_id: 123}` timeout `(number)` The push timeout in milliseconds Instance Members [▸]() resend(timeout) resend(timeout: [number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)) Parameters timeout `(number)` [▸]() send() send() [▸]() receive(status, callback) receive(status: any, callback: any) Parameters status `(any)` callback `(any)` ### Presence Initializes the Presence new Presence(channel: [Channel](index.html#channel), opts: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)) Parameters channel `(Channel)` The Channel opts `(Object = {})` The options, for example `{events: {state: "state", diff: "diff"}}` Static Members [▸]() syncState(currentState, newState, onJoin, onLeave) Used to sync the list of presences on the server with the client's state. An optional `onJoin` and `onLeave` callback can be provided to react to changes in the client's local presences across disconnects and reconnects with the server. syncState(currentState: any, newState: any, onJoin: any, onLeave: any): [Presence](index.html#presence) Parameters currentState `(any)` newState `(any)` onJoin `(any)` onLeave `(any)` Returns `Presence`: [▸]() syncDiff(state, diff, onJoin, onLeave) Used to sync a diff of presence join and leave events from the server, as they happen. Like `syncState`, `syncDiff` accepts optional `onJoin` and `onLeave` callbacks to react to a user joining or leaving from a device. syncDiff(state: any, diff: any, onJoin: any, onLeave: any): [Presence](index.html#presence) Parameters state `(any)` diff `(any)` onJoin `(any)` onLeave `(any)` Returns `Presence`: [▸]() list(presences, chooser) Returns the array of presences, with selected metadata. list(presences: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object), chooser: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)): [Presence](index.html#presence) Parameters presences `(Object)` chooser `(Function)` Returns `Presence`: ### Socket Initializes the Socket * For IE8 support use an ES5-shim ([https://github.com/es-shims/es5-shim](https://github.com/es-shims/es5-shim)) new Socket(endPoint: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), opts: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?) Parameters endPoint `(string)` The string WebSocket endpoint, ie, `"ws://example.com/socket"` , `"wss://example.com"` `"/socket"` (inherited host &amp; protocol) opts `(Object? = {})` Optional configuration Name Description opts.transport `Function?` The Websocket Transport, for example WebSocket or Phoenix.LongPoll. Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined. To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`. opts.longPollFallbackMs `Function?` The millisecond time to attempt the primary transport before falling back to the LongPoll transport. Disabled by default. opts.debug `Function?` When true, enables debug logging. Default false. opts.encode `Function?` The function to encode outgoing messages. Defaults to JSON encoder. opts.decode `Function?` The function to decode incoming messages. Defaults to JSON: opts.timeout `number?` The default timeout in milliseconds to trigger push timeouts. Defaults `DEFAULT_TIMEOUT` opts.heartbeatIntervalMs `number?` The millisec interval to send a heartbeat message opts.reconnectAfterMs `number?` The optional function that returns the millisec socket reconnect interval. Defaults to stepped backoff of: opts.rejoinAfterMs `number?` The optional function that returns the millisec rejoin interval for individual channels. opts.logger `Function?` The optional function for specialized logging, ie: opts.longpollerTimeout `number?` The maximum timeout of a long poll AJAX request. Defaults to 20s (double the server long poll timer). opts.params `(Object | function)?` The optional params to pass when connecting opts.binaryType `string?` The binary type to use for binary WebSocket frames. Defaults to "arraybuffer" opts.vsn `vsn?` The serializer's protocol version to send on connect. Defaults to DEFAULT\_VSN. opts.sessionStorage `Object?` An optional Storage compatible object Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is useful when Phoenix won't have access to `sessionStorage` . For example, This could happen if a site loads a cross-domain channel in an iframe. Example usage: ``` class InMemoryStorage { constructor() { this.storage = {} } getItem(keyName) { return this.storage[keyName] || null } removeItem(keyName) { delete this.storage[keyName] } setItem(keyName, keyValue) { this.storage[keyName] = keyValue } } ``` Instance Members [▸]() getLongPollTransport() Returns the LongPoll transport reference getLongPollTransport() [▸]() replaceTransport(newTransport) Disconnects and replaces the active transport replaceTransport(newTransport: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters newTransport `(Function)` The new transport class to instantiate [▸]() protocol() Returns the socket protocol protocol(): [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) Returns `string`: [▸]() endPointURL() The fully qualified socket url endPointURL(): [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) Returns `string`: [▸]() disconnect(callback, code, reason) Disconnects the socket See [https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status\_codes](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes) for valid status codes. disconnect(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function), code: integer, reason: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)) Parameters callback `(Function)` Optional callback which is called after socket is disconnected. code `(integer)` A status code for disconnection (Optional). reason `(string)` A textual description of the reason to disconnect. (Optional) [▸]() connect(params) connect(params: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)) Parameters params `(Object)` The params to send when connecting, for example `{user_id: userToken}` Passing params to connect is deprecated; pass them in the Socket constructor instead: `new Socket("/socket", {params: {user_id: userToken}})`. [▸]() log(kind, msg, data) Logs the message. Override `this.logger` for specialized logging. noops by default log(kind: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), msg: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), data: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)) Parameters kind `(string)` msg `(string)` data `(Object)` [▸]() hasLogger() Returns true if a logger has been set on this socket. hasLogger() [▸]() onOpen(callback) Registers callbacks for connection open events onOpen(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` Example ``` socket.onOpen(function(){ console.info("the socket was opened") }) ``` [▸]() onClose(callback) Registers callbacks for connection close events onClose(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` [▸]() onError(callback) Registers callbacks for connection error events onError(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` Example ``` socket.onError(function(error){ alert("An error occurred") }) ``` [▸]() onMessage(callback) Registers callbacks for connection message events onMessage(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` [▸]() ping(callback) Pings the server and invokes the callback with the RTT in milliseconds ping(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` Returns true if the ping was pushed or false if unable to be pushed. [▸]() connectionState() connectionState(): [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) Returns `string`: [▸]() isConnected() isConnected(): [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) Returns `boolean`: [▸]() off(refs, null-null) Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations. off(refs: any, null-null: refs) Parameters refs `(any)` null-null `(refs)` list of refs returned by calls to `onOpen` , `onClose` , `onError,` and `onMessage` [▸]() channel(topic, chanParams) Initiates a new channel for the given topic channel(topic: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String), chanParams: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)): [Channel](index.html#channel) Parameters topic `(string)` chanParams `(Object = {})` Parameters for the channel Returns `Channel`: [▸]() push(data) push(data: [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)) Parameters data `(Object)` [▸]() makeRef() Return the next message ref, accounting for overflows makeRef(): [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) Returns `string`: ### Timer Creates a timer that accepts a `timerCalc` function to perform calculated timeout retries, such as exponential backoff. new Timer(callback: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function), timerCalc: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)) Parameters callback `(Function)` timerCalc `(Function)` Example ``` let reconnectTimer = new Timer(() => this.connect(), function(tries){ return [1000, 5000, 10000][tries - 1] || 10000 }) reconnectTimer.scheduleTimeout() // fires after 1000 reconnectTimer.scheduleTimeout() // fires after 5000 reconnectTimer.reset() reconnectTimer.scheduleTimeout() // fires after 1000 ``` Instance Members [▸]() scheduleTimeout() Cancels any previous scheduleTimeout and schedules callback scheduleTimeout() === phx-docs/phoenix/Mix.Tasks.Local.Phx.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/installer/lib/mix/tasks/local.phx.ex#L1 "View Source") mix local.phx (Phoenix v1.7.18) Updates the Phoenix project generator locally. ``` $ mix local.phx ``` Accepts the same command line options as `archive.install hex phx_new`. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Digest.Clean.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.digest.clean.ex#L1 "View Source") mix phx.digest.clean (Phoenix v1.7.18) Removes old versions of compiled assets. By default, it will keep the latest version and 2 previous versions as well as any digest created in the last hour. ``` $ mix phx.digest.clean $ mix phx.digest.clean -o /www/public $ mix phx.digest.clean --age 600 --keep 3 $ mix phx.digest.clean --all ``` ## [](Mix.Tasks.Phx.Digest.Clean.html#module-options)Options - `-o, --output` - indicates the path to your compiled assets directory. Defaults to `priv/static` - `--age` - specifies a maximum age (in seconds) for assets. Files older than age that are not in the last `--keep` versions will be removed. Defaults to 3600 (1 hour) - `--keep` - specifies how many previous versions of assets to keep. Defaults to 2 previous versions - `--all` - specifies that all compiled assets (including the manifest) will be removed. Note this overrides the age and keep switches. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Digest.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.digest.ex#L1 "View Source") mix phx.digest (Phoenix v1.7.18) Digests and compresses static files. ``` $ mix phx.digest $ mix phx.digest priv/static -o /www/public ``` The first argument is the path where the static files are located. The `-o` option indicates the path that will be used to save the digested and compressed files. If no path is given, it will use `priv/static` as the input and output path. The output folder will contain: - the original file - the file compressed with gzip - a file containing the original file name and its digest - a compressed file containing the file name and its digest - a cache manifest file Example of generated files: - app.js - app.js.gz - app-eb0a5b9302e8d32828d8a73f137cc8f0.js - app-eb0a5b9302e8d32828d8a73f137cc8f0.js.gz - cache\_manifest.json You can use [`mix phx.digest.clean`](Mix.Tasks.Phx.Digest.Clean.html) to prune stale versions of the assets. If you want to remove all produced files, run `mix phx.digest.clean --all`. ## [](Mix.Tasks.Phx.Digest.html#module-vsn)vsn It is possible to digest the stylesheet asset references without the query string "?vsn=d" with the option `--no-vsn`. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Auth.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.auth.ex#L1 "View Source") mix phx.gen.auth (Phoenix v1.7.18) Generates authentication logic and related views for a resource. ``` $ mix phx.gen.auth Accounts User users ``` The first argument is the context module followed by the schema module and its plural name (used as the schema table name). Additional information and security considerations are detailed in the [`mix phx.gen.auth` guide](mix_phx_gen_auth.html). ## [](Mix.Tasks.Phx.Gen.Auth.html#module-liveview-vs-conventional-controllers-views)LiveView vs conventional Controllers &amp; Views Authentication views can either be generated to use LiveView by passing the `--live` option, or they can use conventional Phoenix Controllers &amp; Views by passing `--no-live`. If neither of these options are provided, a prompt will be displayed. Using the `--live` option is advised if you plan on using LiveView elsewhere in your application. The user experience when navigating between LiveViews can be tightly controlled, allowing you to let your users navigate to authentication views without necessarily triggering a new HTTP request each time (which would result in a full page load). ## [](Mix.Tasks.Phx.Gen.Auth.html#module-password-hashing)Password hashing The password hashing mechanism defaults to `bcrypt` for Unix systems and `pbkdf2` for Windows systems. Both systems use the [Comeonin interface](../comeonin/index.html). The password hashing mechanism can be overridden with the `--hashing-lib` option. The following values are supported: - `bcrypt` - [bcrypt\_elixir](https://hex.pm/packages/bcrypt_elixir) - `pbkdf2` - [pbkdf2\_elixir](https://hex.pm/packages/pbkdf2_elixir) - `argon2` - [argon2\_elixir](https://hex.pm/packages/argon2_elixir) We recommend developers to consider using `argon2`, which is the most robust of all 3. The downside is that `argon2` is quite CPU and memory intensive, and you will need more powerful instances to run your applications on. For more information about choosing these libraries, see the [Comeonin project](https://github.com/riverrun/comeonin). ## [](Mix.Tasks.Phx.Gen.Auth.html#module-web-namespace)Web namespace By default, the controllers and HTML view will be namespaced by the schema name. You can customize the web module namespace by passing the `--web` flag with a module name, for example: ``` $ mix phx.gen.auth Accounts User users --web Warehouse ``` Which would generate the controllers, views, templates and associated tests nested in the `MyAppWeb.Warehouse` namespace: - `lib/my_app_web/controllers/warehouse/user_auth.ex` - `lib/my_app_web/controllers/warehouse/user_confirmation_controller.ex` - `lib/my_app_web/controllers/warehouse/user_confirmation_html.ex` - `lib/my_app_web/controllers/warehouse/user_confirmation_html/new.html.heex` - `test/my_app_web/controllers/warehouse/user_auth_test.exs` - `test/my_app_web/controllers/warehouse/user_confirmation_controller_test.exs` - and so on... ## [](Mix.Tasks.Phx.Gen.Auth.html#module-multiple-invocations)Multiple invocations You can invoke this generator multiple times. This is typically useful if you have distinct resources that go through distinct authentication workflows: ``` $ mix phx.gen.auth Store User users $ mix phx.gen.auth Backoffice Admin admins ``` ## [](Mix.Tasks.Phx.Gen.Auth.html#module-binary-ids)Binary ids The `--binary-id` option causes the generated migration to use `binary_id` for its primary key and foreign keys. ## [](Mix.Tasks.Phx.Gen.Auth.html#module-default-options)Default options This generator uses default options provided in the `:generators` configuration of your application. These are the defaults: ``` config :your_app, :generators, binary_id: false, sample_binary_id: "11111111-1111-1111-1111-111111111111" ``` You can override those options per invocation by providing corresponding switches, e.g. `--no-binary-id` to use normal ids despite the default configuration. ## [](Mix.Tasks.Phx.Gen.Auth.html#module-custom-table-names)Custom table names By default, the table name for the migration and schema will be the plural name provided for the resource. To customize this value, a `--table` option may be provided. For example: ``` $ mix phx.gen.auth Accounts User users --table accounts_users ``` This will cause the generated tables to be named `"accounts_users"` and `"accounts_users_tokens"`. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Cert.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.cert.ex#L1 "View Source") mix phx.gen.cert (Phoenix v1.7.18) Generates a self-signed certificate for HTTPS testing. ``` $ mix phx.gen.cert $ mix phx.gen.cert my-app my-app.local my-app.internal.example.com ``` Creates a private key and a self-signed certificate in PEM format. These files can be referenced in the `certfile` and `keyfile` parameters of an HTTPS Endpoint. WARNING: only use the generated certificate for testing in a closed network environment, such as running a development server on `localhost`. For production, staging, or testing servers on the public internet, obtain a proper certificate, for example from [Let's Encrypt](https://letsencrypt.org). NOTE: when using Google Chrome, open chrome://flags/#allow-insecure-localhost to enable the use of self-signed certificates on `localhost`. ## [](Mix.Tasks.Phx.Gen.Cert.html#module-arguments)Arguments The list of hostnames, if none are specified, defaults to: - localhost Other (optional) arguments: - `--output` (`-o`): the path and base filename for the certificate and key (default: priv/cert/selfsigned) - `--name` (`-n`): the Common Name value in certificate's subject (default: "Self-signed test certificate") Requires OTP 21.3 or later. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Channel.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.channel.ex#L1 "View Source") mix phx.gen.channel (Phoenix v1.7.18) Generates a Phoenix channel. ``` $ mix phx.gen.channel Room ``` Accepts the module name for the channel The generated files will contain: For a regular application: - a channel in `lib/my_app_web/channels` - a channel test in `test/my_app_web/channels` For an umbrella application: - a channel in `apps/my_app_web/lib/app_name_web/channels` - a channel test in `apps/my_app_web/test/my_app_web/channels` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Context.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.context.ex#L1 "View Source") mix phx.gen.context (Phoenix v1.7.18) Generates a context with functions around an Ecto schema. ``` $ mix phx.gen.context Accounts User users name:string age:integer ``` The first argument is the context module followed by the schema module and its plural name (used as the schema table name). The context is an Elixir module that serves as an API boundary for the given resource. A context often holds many related resources. Therefore, if the context already exists, it will be augmented with functions for the given resource. > Note: A resource may also be split over distinct contexts (such as Accounts.User and Payments.User). The schema is responsible for mapping the database fields into an Elixir struct. Overall, this generator will add the following files to `lib/your_app`: - a context module in `accounts.ex`, serving as the API boundary - a schema in `accounts/user.ex`, with a `users` table A migration file for the repository and test files for the context will also be generated. ## [](Mix.Tasks.Phx.Gen.Context.html#module-generating-without-a-schema)Generating without a schema In some cases, you may wish to bootstrap the context module and tests, but leave internal implementation of the context and schema to yourself. Use the `--no-schema` flags to accomplish this. ## [](Mix.Tasks.Phx.Gen.Context.html#module-table)table By default, the table name for the migration and schema will be the plural name provided for the resource. To customize this value, a `--table` option may be provided. For example: ``` $ mix phx.gen.context Accounts User users --table cms_users ``` ## [](Mix.Tasks.Phx.Gen.Context.html#module-binary_id)binary\_id Generated migration can use `binary_id` for schema's primary key and its references with option `--binary-id`. ## [](Mix.Tasks.Phx.Gen.Context.html#module-default-options)Default options This generator uses default options provided in the `:generators` configuration of your application. These are the defaults: ``` config :your_app, :generators, migration: true, binary_id: false, timestamp_type: :naive_datetime, sample_binary_id: "11111111-1111-1111-1111-111111111111" ``` You can override those options per invocation by providing corresponding switches, e.g. `--no-binary-id` to use normal ids despite the default configuration or `--migration` to force generation of the migration. Read the documentation for `phx.gen.schema` for more information on attributes. ## [](Mix.Tasks.Phx.Gen.Context.html#module-skipping-prompts)Skipping prompts This generator will prompt you if there is an existing context with the same name, in order to provide more instructions on how to correctly use phoenix contexts. You can skip this prompt and automatically merge the new schema access functions and tests into the existing context using `--merge-with-existing-context`. To prevent changes to the existing context and exit the generator, use `--no-merge-with-existing-context`. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Embedded.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.embedded.ex#L1 "View Source") mix phx.gen.embedded (Phoenix v1.7.18) Generates an embedded Ecto schema for casting/validating data outside the DB. ``` mix phx.gen.embedded Blog.Post title:string views:integer ``` The first argument is the schema module followed by the schema attributes. The generated schema above will contain: - an embedded schema file in `lib/my_app/blog/post.ex` ## [](Mix.Tasks.Phx.Gen.Embedded.html#module-attributes)Attributes The resource fields are given using `name:type` syntax where type are the types supported by Ecto. Omitting the type makes it default to `:string`: ``` mix phx.gen.embedded Blog.Post title views:integer ``` The following types are supported: - `:integer` - `:float` - `:decimal` - `:boolean` - `:map` - `:string` - `:array` - `:references` - `:text` - `:date` - `:time` - `:time_usec` - `:naive_datetime` - `:naive_datetime_usec` - `:utc_datetime` - `:utc_datetime_usec` - `:uuid` - `:binary` - `:enum` - `:datetime` - An alias for `:naive_datetime` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Html.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.html.ex#L1 "View Source") mix phx.gen.html (Phoenix v1.7.18) Generates controller with view, templates, schema and context for an HTML resource. ``` mix phx.gen.html Accounts User users name:string age:integer ``` The first argument, `Accounts`, is the resource's context. A context is an Elixir module that serves as an API boundary for closely related resources. The second argument, `User`, is the resource's schema. A schema is an Elixir module responsible for mapping database fields into an Elixir struct. The `User` schema above specifies two fields with their respective colon-delimited data types: `name:string` and `age:integer`. See [`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) for more information on attributes. > Note: A resource may also be split over distinct contexts (such as `Accounts.User` and `Payments.User`). This generator adds the following files to `lib/`: - a controller in `lib/my_app_web/controllers/user_controller.ex` - default CRUD HTML templates in `lib/my_app_web/controllers/user_html` - an HTML view collocated with the controller in `lib/my_app_web/controllers/user_html.ex` - a schema in `lib/my_app/accounts/user.ex`, with an `users` table - a context module in `lib/my_app/accounts.ex` for the accounts API Additionally, this generator creates the following files: - a migration for the schema in `priv/repo/migrations` - a controller test module in `test/my_app/controllers/user_controller_test.exs` - a context test module in `test/my_app/accounts_test.exs` - a context test helper module in `test/support/fixtures/accounts_fixtures.ex` If the context already exists, this generator injects functions for the given resource into the context, context test, and context test helper modules. ## [](Mix.Tasks.Phx.Gen.Html.html#module-umbrella-app-configuration)Umbrella app configuration By default, Phoenix injects both web and domain specific functionality into the same application. When using umbrella applications, those concerns are typically broken into two separate apps, your context application - let's call it `my_app` - and its web layer, which Phoenix assumes to be `my_app_web`. You can teach Phoenix to use this style via the `:context_app` configuration option in your `my_app_umbrella/config/config.exs`: ``` config :my_app_web, ecto_repos: [Stuff.Repo], generators: [context_app: :my_app] ``` Alternatively, the `--context-app` option may be supplied to the generator: ``` mix phx.gen.html Sales User users --context-app my_app ``` If you delete the `:context_app` configuration option, Phoenix will automatically put generated web files in `my_app_umbrella/apps/my_app_web_web`. If you change the value of `:context_app` to `:new_value`, `my_app_umbrella/apps/new_value_web` must already exist or you will get the following error: \** (Mix) no directory for context\_app :new\_value found in my\_app\_web's deps. ## [](Mix.Tasks.Phx.Gen.Html.html#module-web-namespace)Web namespace By default, the controller and HTML view will be namespaced by the schema name. You can customize the web module namespace by passing the `--web` flag with a module name, for example: ``` mix phx.gen.html Sales User users --web Sales ``` Which would generate a `lib/app_web/controllers/sales/user_controller.ex` and `lib/app_web/controllers/sales/user_html.ex`. ## [](Mix.Tasks.Phx.Gen.Html.html#module-customizing-the-context-schema-tables-and-migrations)Customizing the context, schema, tables and migrations In some cases, you may wish to bootstrap HTML templates, controllers, and controller tests, but leave internal implementation of the context or schema to yourself. You can use the `--no-context` and `--no-schema` flags for file generation control. You can also change the table name or configure the migrations to use binary ids for primary keys, see [`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) for more information. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Json.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.json.ex#L1 "View Source") mix phx.gen.json (Phoenix v1.7.18) Generates controller, JSON view, and context for a JSON resource. ``` mix phx.gen.json Accounts User users name:string age:integer ``` The first argument is the context module followed by the schema module and its plural name (used as the schema table name). The context is an Elixir module that serves as an API boundary for the given resource. A context often holds many related resources. Therefore, if the context already exists, it will be augmented with functions for the given resource. > Note: A resource may also be split over distinct contexts (such as `Accounts.User` and `Payments.User`). The schema is responsible for mapping the database fields into an Elixir struct. It is followed by an optional list of attributes, with their respective names and types. See [`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) for more information on attributes. Overall, this generator will add the following files to `lib/`: - a context module in `lib/app/accounts.ex` for the accounts API - a schema in `lib/app/accounts/user.ex`, with an `users` table - a controller in `lib/app_web/controllers/user_controller.ex` - a JSON view collocated with the controller in `lib/app_web/controllers/user_json.ex` A migration file for the repository and test files for the context and controller features will also be generated. ## [](Mix.Tasks.Phx.Gen.Json.html#module-api-prefix)API Prefix By default, the prefix "/api" will be generated for API route paths. This can be customized via the `:api_prefix` generators configuration: ``` config :your_app, :generators, api_prefix: "/api/v1" ``` ## [](Mix.Tasks.Phx.Gen.Json.html#module-the-context-app)The context app The location of the web files (controllers, json views, etc) in an umbrella application will vary based on the `:context_app` config located in your applications `:generators` configuration. When set, the Phoenix generators will generate web files directly in your lib and test folders since the application is assumed to be isolated to web specific functionality. If `:context_app` is not set, the generators will place web related lib and test files in a `web/` directory since the application is assumed to be handling both web and domain specific functionality. Example configuration: ``` config :my_app_web, :generators, context_app: :my_app ``` Alternatively, the `--context-app` option may be supplied to the generator: ``` mix phx.gen.json Sales User users --context-app warehouse ``` ## [](Mix.Tasks.Phx.Gen.Json.html#module-web-namespace)Web namespace By default, the controller and json view will be namespaced by the schema name. You can customize the web module namespace by passing the `--web` flag with a module name, for example: ``` mix phx.gen.json Sales User users --web Sales ``` Which would generate a `lib/app_web/controllers/sales/user_controller.ex` and `lib/app_web/controller/sales/user_json.ex`. ## [](Mix.Tasks.Phx.Gen.Json.html#module-customizing-the-context-schema-tables-and-migrations)Customizing the context, schema, tables and migrations In some cases, you may wish to bootstrap JSON views, controllers, and controller tests, but leave internal implementation of the context or schema to yourself. You can use the `--no-context` and `--no-schema` flags for file generation control. You can also change the table name or configure the migrations to use binary ids for primary keys, see [`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) for more information. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Live.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.live.ex#L1 "View Source") mix phx.gen.live (Phoenix v1.7.18) Generates LiveView, templates, and context for a resource. ``` mix phx.gen.live Accounts User users name:string age:integer ``` The first argument is the context module. The context is an Elixir module that serves as an API boundary for the given resource. A context often holds many related resources. Therefore, if the context already exists, it will be augmented with functions for the given resource. The second argument is the schema module. The schema is responsible for mapping the database fields into an Elixir struct. The remaining arguments are the schema module plural name (used as the schema table name), and an optional list of attributes as their respective names and types. See [`mix help phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) for more information on attributes. When this command is run for the first time, a `Components` module will be created if it does not exist, along with the resource level LiveViews and components, including `UserLive.Index`, `UserLive.Show`, and `UserLive.FormComponent` modules for the new resource. > Note: A resource may also be split over distinct contexts (such as `Accounts.User` and `Payments.User`). Overall, this generator will add the following files: - a context module in `lib/app/accounts.ex` for the accounts API - a schema in `lib/app/accounts/user.ex`, with a `users` table - a LiveView in `lib/app_web/live/user_live/show.ex` - a LiveView in `lib/app_web/live/user_live/index.ex` - a LiveComponent in `lib/app_web/live/user_live/form_component.ex` - a helpers module in `lib/app_web/live/live_helpers.ex` with a modal After file generation is complete, there will be output regarding required updates to the `lib/app_web/router.ex` file. ``` Add the live routes to your browser scope in lib/app_web/router.ex: live "/users", UserLive.Index, :index live "/users/new", UserLive.Index, :new live "/users/:id/edit", UserLive.Index, :edit live "/users/:id", UserLive.Show, :show live "/users/:id/show/edit", UserLive.Show, :edit ``` ## [](Mix.Tasks.Phx.Gen.Live.html#module-the-context-app)The context app A migration file for the repository and test files for the context and controller features will also be generated. The location of the web files (LiveView's, views, templates, etc.) in an umbrella application will vary based on the `:context_app` config located in your applications `:generators` configuration. When set, the Phoenix generators will generate web files directly in your lib and test folders since the application is assumed to be isolated to web specific functionality. If `:context_app` is not set, the generators will place web related lib and test files in a `web/` directory since the application is assumed to be handling both web and domain specific functionality. Example configuration: ``` config :my_app_web, :generators, context_app: :my_app ``` Alternatively, the `--context-app` option may be supplied to the generator: ``` mix phx.gen.live Accounts User users --context-app warehouse ``` ## [](Mix.Tasks.Phx.Gen.Live.html#module-web-namespace)Web namespace By default, the LiveView modules will be namespaced by the web module. You can customize the web module namespace by passing the `--web` flag with a module name, for example: ``` mix phx.gen.live Accounts User users --web Sales ``` Which would generate the LiveViews in `lib/app_web/live/sales/user_live/`, namespaced `AppWeb.Sales.UserLive` instead of `AppWeb.UserLive`. ## [](Mix.Tasks.Phx.Gen.Live.html#module-customizing-the-context-schema-tables-and-migrations)Customizing the context, schema, tables and migrations In some cases, you may wish to bootstrap HTML templates, LiveViews, and tests, but leave internal implementation of the context or schema to yourself. You can use the `--no-context` and `--no-schema` flags for file generation control. ``` mix phx.gen.live Accounts User users --no-context --no-schema ``` In the cases above, tests are still generated, but they will all fail. You can also change the table name or configure the migrations to use binary ids for primary keys, see [`mix help phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) for more information. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Notifier.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.notifier.ex#L1 "View Source") mix phx.gen.notifier (Phoenix v1.7.18) Generates a notifier that delivers emails by default. ``` $ mix phx.gen.notifier Accounts User welcome_user reset_password confirmation_instructions ``` This task expects a context module name, followed by a notifier name and one or more message names. Messages are the functions that will be created prefixed by "deliver", so the message name should be "snake\_case" without punctuation. Additionally a context app can be specified with the flag `--context-app`, which is useful if the notifier is being generated in a different app under an umbrella. ``` $ mix phx.gen.notifier Accounts User welcome_user --context-app marketing ``` The app "marketing" must exist before the command is executed. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Presence.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.presence.ex#L1 "View Source") mix phx.gen.presence (Phoenix v1.7.18) Generates a Presence tracker. ``` $ mix phx.gen.presence $ mix phx.gen.presence MyPresence ``` The argument, which defaults to `Presence`, defines the module name of the Presence tracker. Generates a new file, `lib/my_app_web/channels/my_presence.ex`, where `my_presence` is the snake-cased version of the provided module name. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Release.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.release.ex#L1 "View Source") mix phx.gen.release (Phoenix v1.7.18) Generates release files and optional Dockerfile for release-based deployments. The following release files are created: - `lib/app_name/release.ex` - A release module containing tasks for running migrations inside a release - `rel/overlays/bin/migrate` - A migrate script for conveniently invoking the release system migrations - `rel/overlays/bin/server` - A server script for conveniently invoking the release system with environment variables to start the phoenix web server Note, the `rel/overlays` directory is copied into the release build by default when running [`mix release`](../mix/Mix.Tasks.Release.html). To skip generating the migration-related files, use the `--no-ecto` flag. To force these migration-related files to be generated, the use `--ecto` flag. ## [](Mix.Tasks.Phx.Gen.Release.html#module-docker)Docker When the `--docker` flag is passed, the following docker files are generated: - `Dockerfile` - The Dockerfile for use in any standard docker deployment - `.dockerignore` - A docker ignore file with standard elixir defaults For extended release configuration, the [`mix release.init`](../mix/Mix.Tasks.Release.Init.html) task can be used in addition to this task. See the [`Mix.Release`](../mix/Mix.Release.html) docs for more details. # [](Mix.Tasks.Phx.Gen.Release.html#summary)Summary ## [Functions](Mix.Tasks.Phx.Gen.Release.html#functions) [otp\_vsn()](Mix.Tasks.Phx.Gen.Release.html#otp_vsn/0) # [](Mix.Tasks.Phx.Gen.Release.html#functions)Functions [](Mix.Tasks.Phx.Gen.Release.html#otp_vsn/0) # otp\_vsn() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.release.ex#L317) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Schema.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.schema.ex#L1 "View Source") mix phx.gen.schema (Phoenix v1.7.18) Generates an Ecto schema and migration. ``` $ mix phx.gen.schema Blog.Post blog_posts title:string views:integer ``` The first argument is the schema module followed by its plural name (used as the table name). The generated schema above will contain: - a schema file in `lib/my_app/blog/post.ex`, with a `blog_posts` table - a migration file for the repository The generated migration can be skipped with `--no-migration`. ## [](Mix.Tasks.Phx.Gen.Schema.html#module-contexts)Contexts Your schemas can be generated and added to a separate OTP app. Make sure your configuration is properly setup or manually specify the context app with the `--context-app` option with the CLI. Via config: ``` config :marketing_web, :generators, context_app: :marketing ``` Via CLI: ``` $ mix phx.gen.schema Blog.Post blog_posts title:string views:integer --context-app marketing ``` ## [](Mix.Tasks.Phx.Gen.Schema.html#module-attributes)Attributes The resource fields are given using `name:type` syntax where type are the types supported by Ecto. Omitting the type makes it default to `:string`: ``` $ mix phx.gen.schema Blog.Post blog_posts title views:integer ``` The following types are supported: - `:integer` - `:float` - `:decimal` - `:boolean` - `:map` - `:string` - `:array` - `:references` - `:text` - `:date` - `:time` - `:time_usec` - `:naive_datetime` - `:naive_datetime_usec` - `:utc_datetime` - `:utc_datetime_usec` - `:uuid` - `:binary` - `:enum` - `:datetime` - An alias for `:naive_datetime` The generator also supports references, which we will properly associate the given column to the primary key column of the referenced table: ``` $ mix phx.gen.schema Blog.Post blog_posts title user_id:references:users ``` This will result in a migration with an `:integer` column of `:user_id` and create an index. Furthermore an array type can also be given if it is supported by your database, although it requires the type of the underlying array element to be given too: ``` $ mix phx.gen.schema Blog.Post blog_posts tags:array:string ``` Unique columns can be automatically generated by using: ``` $ mix phx.gen.schema Blog.Post blog_posts title:unique unique_int:integer:unique ``` Redact columns can be automatically generated by using: ``` $ mix phx.gen.schema Accounts.Superhero superheroes secret_identity:redact password:string:redact ``` Ecto.Enum fields can be generated by using: ``` $ mix phx.gen.schema Blog.Post blog_posts title status:enum:unpublished:published:deleted ``` If no data type is given, it defaults to a string. ## [](Mix.Tasks.Phx.Gen.Schema.html#module-table)table By default, the table name for the migration and schema will be the plural name provided for the resource. To customize this value, a `--table` option may be provided. For example: ``` $ mix phx.gen.schema Blog.Post posts --table cms_posts ``` ## [](Mix.Tasks.Phx.Gen.Schema.html#module-binary_id)binary\_id Generated migration can use `binary_id` for schema's primary key and its references with option `--binary-id`. ## [](Mix.Tasks.Phx.Gen.Schema.html#module-repo)repo Generated migration can use `repo` to set the migration repository folder with option `--repo`: ``` $ mix phx.gen.schema Blog.Post posts --repo MyApp.Repo.Auth ``` ## [](Mix.Tasks.Phx.Gen.Schema.html#module-migration_dir)migration\_dir Generated migrations can be added to a specific `--migration-dir` which sets the migration folder path: ``` $ mix phx.gen.schema Blog.Post posts --migration-dir /path/to/directory ``` ## [](Mix.Tasks.Phx.Gen.Schema.html#module-prefix)prefix By default migrations and schemas are generated without a prefix. For PostgreSQL this sets the "SCHEMA" (typically set via `search_path`) and for MySQL it sets the database for the generated migration and schema. The prefix can be used to thematically organize your tables on the database level. A prefix can be specified with the `--prefix` flags. For example: ``` $ mix phx.gen.schema Blog.Post posts --prefix blog ``` #### Warning The flag does not generate migrations to create the schema / database. This needs to be done manually or in a separate migration. ## [](Mix.Tasks.Phx.Gen.Schema.html#module-default-options)Default options This generator uses default options provided in the `:generators` configuration of your application. These are the defaults: ``` config :your_app, :generators, migration: true, binary_id: false, timestamp_type: :naive_datetime, sample_binary_id: "11111111-1111-1111-1111-111111111111" ``` You can override those options per invocation by providing corresponding switches, e.g. `--no-binary-id` to use normal ids despite the default configuration or `--migration` to force generation of the migration. ## [](Mix.Tasks.Phx.Gen.Schema.html#module-utc-timestamps)UTC timestamps By setting the `:timestamp_type` to `:utc_datetime`, the timestamps will be created using the UTC timezone. This results in a [`DateTime`](../elixir/DateTime.html) struct instead of a [`NaiveDateTime`](../elixir/NaiveDateTime.html). This can also be set to `:utc_datetime_usec` for microsecond precision. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Secret.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.secret.ex#L1 "View Source") mix phx.gen.secret (Phoenix v1.7.18) Generates a secret and prints it to the terminal. ``` $ mix phx.gen.secret [length] ``` By default, mix phx.gen.secret generates a key 64 characters long. The minimum value for `length` is 32. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.Socket.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.socket.ex#L1 "View Source") mix phx.gen.socket (Phoenix v1.7.18) Generates a Phoenix socket handler. ``` $ mix phx.gen.socket User ``` Accepts the module name for the socket. The generated files will contain: For a regular application: - a client in `assets/js` - a socket in `lib/my_app_web/channels` For an umbrella application: - a client in `apps/my_app_web/assets/js` - a socket in `apps/my_app_web/lib/my_app_web/channels` You can then generate channels with [`mix phx.gen.channel`](Mix.Tasks.Phx.Gen.Channel.html). [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Gen.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.ex#L1 "View Source") mix phx.gen (Phoenix v1.7.18) Lists all available Phoenix generators. ## [](Mix.Tasks.Phx.Gen.html#module-crud-related-generators)CRUD related generators The table below shows a summary of the contents created by the CRUD generators: TaskSchemaMigrationContextControllerViewLiveView`phx.gen.embedded`x`phx.gen.schema`xx`phx.gen.context`xxx`phx.gen.live`xxxx`phx.gen.json`xxxxx`phx.gen.html`xxxxx # [](Mix.Tasks.Phx.Gen.html#summary)Summary ## [Functions](Mix.Tasks.Phx.Gen.html#functions) [run(args)](Mix.Tasks.Phx.Gen.html#run/1) Callback implementation for [`Mix.Task.run/1`](../mix/Mix.Task.html#c:run/1). # [](Mix.Tasks.Phx.Gen.html#functions)Functions [](Mix.Tasks.Phx.Gen.html#run/1) # run(args) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.gen.ex#L23) Callback implementation for [`Mix.Task.run/1`](../mix/Mix.Task.html#c:run/1). [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.New.Ecto.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/installer/lib/mix/tasks/phx.new.ecto.ex#L1 "View Source") mix phx.new.ecto (Phoenix v1.7.18) Creates a new Ecto project within an umbrella project. This task is intended to create a bare Ecto project without web integration, which serves as a core application of your domain for web applications and your greater umbrella platform to integrate with. It expects the name of the project as an argument. ``` $ cd my_umbrella/apps $ mix phx.new.ecto APP [--module MODULE] [--app APP] ``` A project at the given APP directory will be created. The application name and module name will be retrieved from the application name, unless `--module` or `--app` is given. ## [](Mix.Tasks.Phx.New.Ecto.html#module-options)Options - `--app` - the name of the OTP application - `--module` - the name of the base module in the generated skeleton - `--database` - specify the database adapter for Ecto. One of: - `postgres` - via [https://github.com/elixir-ecto/postgrex](https://github.com/elixir-ecto/postgrex) - `mysql` - via [https://github.com/elixir-ecto/myxql](https://github.com/elixir-ecto/myxql) - `mssql` - via [https://github.com/livehelpnow/tds](https://github.com/livehelpnow/tds) - `sqlite3` - via [https://github.com/elixir-sqlite/ecto\_sqlite3](https://github.com/elixir-sqlite/ecto_sqlite3) Please check the driver docs for more information and requirements. Defaults to "postgres". - `--binary-id` - use `binary_id` as primary key type in Ecto schemas ## [](Mix.Tasks.Phx.New.Ecto.html#module-examples)Examples ``` $ mix phx.new.ecto hello_ecto ``` Is equivalent to: ``` $ mix phx.new.ecto hello_ecto --module HelloEcto ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.New.Web.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/installer/lib/mix/tasks/phx.new.web.ex#L1 "View Source") mix phx.new.web (Phoenix v1.7.18) Creates a new Phoenix web project within an umbrella project. It expects the name of the OTP app as the first argument and for the command to be run inside your umbrella application's apps directory: ``` $ cd my_umbrella/apps $ mix phx.new.web APP [--module MODULE] [--app APP] ``` This task is intended to create a bare Phoenix project without database integration, which interfaces with your greater umbrella application(s). ## [](Mix.Tasks.Phx.New.Web.html#module-examples)Examples ``` $ mix phx.new.web hello_web ``` Is equivalent to: ``` $ mix phx.new.web hello_web --module HelloWeb ``` Supports the same options as the `phx.new` task. See [`Mix.Tasks.Phx.New`](Mix.Tasks.Phx.New.html) for details. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.New.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/installer/lib/mix/tasks/phx.new.ex#L1 "View Source") mix phx.new (Phoenix v1.7.18) Creates a new Phoenix project. It expects the path of the project as an argument. ``` $ mix phx.new PATH [--module MODULE] [--app APP] ``` A project at the given PATH will be created. The application name and module name will be retrieved from the path, unless `--module` or `--app` is given. ## [](Mix.Tasks.Phx.New.html#module-options)Options - `--umbrella` - generate an umbrella project, with one application for your domain, and a second application for the web interface. - `--app` - the name of the OTP application - `--module` - the name of the base module in the generated skeleton - `--database` - specify the database adapter for Ecto. One of: - `postgres` - via [https://github.com/elixir-ecto/postgrex](https://github.com/elixir-ecto/postgrex) - `mysql` - via [https://github.com/elixir-ecto/myxql](https://github.com/elixir-ecto/myxql) - `mssql` - via [https://github.com/livehelpnow/tds](https://github.com/livehelpnow/tds) - `sqlite3` - via [https://github.com/elixir-sqlite/ecto\_sqlite3](https://github.com/elixir-sqlite/ecto_sqlite3) Please check the driver docs for more information and requirements. Defaults to "postgres". - `--adapter` - specify the http adapter. One of: - `cowboy` - via [https://github.com/elixir-plug/plug\_cowboy](https://github.com/elixir-plug/plug_cowboy) - `bandit` - via [https://github.com/mtrudel/bandit](https://github.com/mtrudel/bandit) Please check the adapter docs for more information and requirements. Defaults to "bandit". - `--no-assets` - equivalent to `--no-esbuild` and `--no-tailwind` - `--no-dashboard` - do not include Phoenix.LiveDashboard - `--no-ecto` - do not generate Ecto files - `--no-esbuild` - do not include esbuild dependencies and assets. We do not recommend setting this option, unless for API only applications, as doing so requires you to manually add and track JavaScript dependencies - `--no-gettext` - do not generate gettext files - `--no-html` - do not generate HTML views - `--no-live` - comment out LiveView socket setup in your Endpoint and assets/js/app.js. Automatically disabled if --no-html is given - `--no-mailer` - do not generate Swoosh mailer files - `--no-tailwind` - do not include tailwind dependencies and assets. The generated markup will still include Tailwind CSS classes, those are left-in as reference for the subsequent styling of your layout and components - `--binary-id` - use `binary_id` as primary key type in Ecto schemas - `--verbose` - use verbose output - `-v`, `--version` - prints the Phoenix installer version When passing the `--no-ecto` flag, Phoenix generators such as `phx.gen.html`, `phx.gen.json`, `phx.gen.live`, and `phx.gen.context` may no longer work as expected as they generate context files that rely on Ecto for the database access. In those cases, you can pass the `--no-context` flag to generate most of the HTML and JSON files but skip the context, allowing you to fill in the blanks as desired. Similarly, if `--no-html` is given, the files generated by `phx.gen.html` will no longer work, as important HTML components will be missing. ## [](Mix.Tasks.Phx.New.html#module-installation)Installation [`mix phx.new`](Mix.Tasks.Phx.New.html) by default prompts you to fetch and install your dependencies. You can enable this behaviour by passing the `--install` flag or disable it with the `--no-install` flag. ## [](Mix.Tasks.Phx.New.html#module-examples)Examples ``` $ mix phx.new hello_world ``` Is equivalent to: ``` $ mix phx.new hello_world --module HelloWorld ``` Or without the HTML and JS bits (useful for APIs): ``` $ mix phx.new ~/Workspace/hello_world --no-html --no-assets ``` As an umbrella: ``` $ mix phx.new hello --umbrella ``` Would generate the following directory structure and modules: ``` hello_umbrella/ Hello.Umbrella apps/ hello/ Hello hello_web/ HelloWeb ``` You can read more about umbrella projects using the official [Elixir guide](../elixir/dependencies-and-umbrella-projects.html#umbrella-projects) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Routes.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.routes.ex#L1 "View Source") mix phx.routes (Phoenix v1.7.18) Prints all routes for the default or a given router. Can also locate the controller function behind a specified url. ``` $ mix phx.routes [ROUTER] [--info URL] ``` The default router is inflected from the application name unless a configuration named `:namespace` is set inside your application configuration. For example, the configuration: ``` config :my_app, namespace: My.App ``` will exhibit the routes for `My.App.Router` when this task is invoked without arguments. Umbrella projects do not have a default router and therefore always expect a router to be given. An alias can be added to mix.exs to automate this: ``` defp aliases do [ "phx.routes": "phx.routes MyAppWeb.Router", # aliases... ] ``` ## [](Mix.Tasks.Phx.Routes.html#module-options)Options - `--info` - locate the controller function definition called by the given url - `--method` - what HTTP method to use with the given url, only works when used with `--info` and defaults to `get` ## [](Mix.Tasks.Phx.Routes.html#module-examples)Examples Print all routes for the default router: ``` $ mix phx.routes ``` Print all routes for the given router: ``` $ mix phx.routes MyApp.AnotherRouter ``` Print information about the controller function called by a specified url: ``` $ mix phx.routes --info http://0.0.0.0:4000/home Module: RouteInfoTestWeb.PageController Function: :index /home/my_app/controllers/page_controller.ex:4 ``` Print information about the controller function called by a specified url and HTTP method: ``` $ mix phx.routes --info http://0.0.0.0:4000/users --method post Module: RouteInfoTestWeb.UserController Function: :create /home/my_app/controllers/user_controller.ex:24 ``` # [](Mix.Tasks.Phx.Routes.html#summary)Summary ## [Functions](Mix.Tasks.Phx.Routes.html#functions) [get\_url\_info(url, arg)](Mix.Tasks.Phx.Routes.html#get_url_info/2) # [](Mix.Tasks.Phx.Routes.html#functions)Functions [](Mix.Tasks.Phx.Routes.html#get_url_info/2) # get\_url\_info(url, arg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.routes.ex#L92) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.Server.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.server.ex#L1 "View Source") mix phx.server (Phoenix v1.7.18) Starts the application by configuring all endpoints servers to run. Note: to start the endpoint without using this mix task you must set `server: true` in your [`Phoenix.Endpoint`](Phoenix.Endpoint.html) configuration. ## [](Mix.Tasks.Phx.Server.html#module-command-line-options)Command line options - `--open` - open browser window for each started endpoint Furthermore, this task accepts the same command-line options as [`mix run`](../mix/Mix.Tasks.Run.html). For example, to run `phx.server` without recompiling: ``` $ mix phx.server --no-compile ``` The `--no-halt` flag is automatically added. Note that the `--no-deps-check` flag cannot be used this way, because Mix needs to check dependencies to find `phx.server`. To run `phx.server` without checking dependencies, you can run: ``` $ mix do deps.loadpaths --no-deps-check, phx.server ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Mix.Tasks.Phx.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/mix/tasks/phx.ex#L1 "View Source") mix phx (Phoenix v1.7.18) Prints Phoenix tasks and their information. ``` $ mix phx ``` To print the Phoenix version, pass `-v` or `--version`, for example: ``` $ mix phx --version ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.ActionClauseError.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/exceptions.ex#L41 "View Source") Phoenix.ActionClauseError exception (Phoenix v1.7.18) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Channel.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L1 "View Source") Phoenix.Channel behaviour (Phoenix v1.7.18) Defines a Phoenix Channel. Channels provide a means for bidirectional communication from clients that integrate with the [`Phoenix.PubSub`](../phoenix_pubsub/2.1.3/Phoenix.PubSub.html) layer for soft-realtime functionality. For a conceptual overview, see the [Channels guide](channels.html). ## [](Phoenix.Channel.html#module-topics-callbacks)Topics &amp; Callbacks Every time you join a channel, you need to choose which particular topic you want to listen to. The topic is just an identifier, but by convention it is often made of two parts: `"topic:subtopic"`. Using the `"topic:subtopic"` approach pairs nicely with the [`Phoenix.Socket.channel/3`](Phoenix.Socket.html#channel/3) allowing you to match on all topics starting with a given prefix by using a splat (the `*` character) as the last character in the topic pattern: ``` channel "room:*", MyAppWeb.RoomChannel ``` Any topic coming into the router with the `"room:"` prefix would dispatch to `MyAppWeb.RoomChannel` in the above example. Topics can also be pattern matched in your channels' `join/3` callback to pluck out the scoped pattern: ``` # handles the special `"lobby"` subtopic def join("room:lobby", _payload, socket) do {:ok, socket} end # handles any other subtopic as the room ID, for example `"room:12"`, `"room:34"` def join("room:" <> room_id, _payload, socket) do {:ok, socket} end ``` ## [](Phoenix.Channel.html#module-authorization)Authorization Clients must join a channel to send and receive PubSub events on that channel. Your channels must implement a `join/3` callback that authorizes the socket for the given topic. For example, you could check if the user is allowed to join that particular room. To authorize a socket in `join/3`, return `{:ok, socket}`. To refuse authorization in `join/3`, return `{:error, reply}`. ## [](Phoenix.Channel.html#module-incoming-events)Incoming Events After a client has successfully joined a channel, incoming events from the client are routed through the channel's `handle_in/3` callbacks. Within these callbacks, you can perform any action. Incoming callbacks must return the `socket` to maintain ephemeral state. Typically you'll either forward a message to all listeners with [`broadcast!/3`](Phoenix.Channel.html#broadcast!/3) or reply directly to a client event for request/response style messaging. General message payloads are received as maps: ``` def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do ... {:reply, :ok, socket} end ``` Binary data payloads are passed as a `{:binary, data}` tuple: ``` def handle_in("file_chunk", {:binary, chunk}, socket) do ... {:reply, :ok, socket} end ``` ## [](Phoenix.Channel.html#module-broadcasts)Broadcasts Here's an example of receiving an incoming `"new_msg"` event from one client, and broadcasting the message to all topic subscribers for this socket. ``` def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do broadcast!(socket, "new_msg", %{uid: uid, body: body}) {:noreply, socket} end ``` ## [](Phoenix.Channel.html#module-replies)Replies Replies are useful for acknowledging a client's message or responding with the results of an operation. A reply is sent only to the client connected to the current channel process. Behind the scenes, they include the client message `ref`, which allows the client to correlate the reply it receives with the message it sent. For example, imagine creating a resource and replying with the created record: ``` def handle_in("create:post", attrs, socket) do changeset = Post.changeset(%Post{}, attrs) if changeset.valid? do post = Repo.insert!(changeset) response = MyAppWeb.PostView.render("show.json", %{post: post}) {:reply, {:ok, response}, socket} else response = MyAppWeb.ChangesetView.render("errors.json", %{changeset: changeset}) {:reply, {:error, response}, socket} end end ``` Or you may just want to confirm that the operation succeeded: ``` def handle_in("create:post", attrs, socket) do changeset = Post.changeset(%Post{}, attrs) if changeset.valid? do Repo.insert!(changeset) {:reply, :ok, socket} else {:reply, :error, socket} end end ``` Binary data is also supported with replies via a `{:binary, data}` tuple: ``` {:reply, {:ok, {:binary, bin}}, socket} ``` If you don't want to send a reply to the client, you can return: ``` {:noreply, socket} ``` One situation when you might do this is if you need to reply later; see [`reply/2`](Phoenix.Channel.html#reply/2). ## [](Phoenix.Channel.html#module-pushes)Pushes Calling [`push/3`](Phoenix.Channel.html#push/3) allows you to send a message to the client which is not a reply to a specific client message. Because it is not a reply, a pushed message does not contain a client message `ref`; there is no prior client message to relate it to. Possible use cases include notifying a client that: - You've auto-saved the user's document - The user's game is ending soon - The IoT device's settings should be updated For example, you could [`push/3`](Phoenix.Channel.html#push/3) a message to the client in `handle_info/3` after receiving a `PubSub` message relevant to them. ``` alias Phoenix.Socket.Broadcast def handle_info(%Broadcast{topic: _, event: event, payload: payload}, socket) do push(socket, event, payload) {:noreply, socket} end ``` Push data can be given in the form of a map or a tagged `{:binary, data}` tuple: ``` # client asks for their current rank. reply contains it, and client # is also pushed a leader board and a badge image def handle_in("current_rank", _, socket) do push(socket, "leaders", %{leaders: Game.get_leaders(socket.assigns.game_id)}) push(socket, "badge", {:binary, File.read!(socket.assigns.badge_path)}) {:reply, %{val: Game.get_rank(socket.assigns[:user])}, socket} end ``` Note that in this example, [`push/3`](Phoenix.Channel.html#push/3) is called from `handle_in/3`; in this way you can essentially reply N times to a single message from the client. See [`reply/2`](Phoenix.Channel.html#reply/2) for why this may be desirable. ## [](Phoenix.Channel.html#module-intercepting-outgoing-events)Intercepting Outgoing Events When an event is broadcasted with [`broadcast/3`](Phoenix.Channel.html#broadcast/3), each channel subscriber can choose to intercept the event and have their `handle_out/3` callback triggered. This allows the event's payload to be customized on a socket by socket basis to append extra information, or conditionally filter the message from being delivered. If the event is not intercepted with [`Phoenix.Channel.intercept/1`](Phoenix.Channel.html#intercept/1), then the message is pushed directly to the client: ``` intercept ["new_msg", "user_joined"] # for every socket subscribing to this topic, append an `is_editable` # value for client metadata. def handle_out("new_msg", msg, socket) do push(socket, "new_msg", Map.merge(msg, %{is_editable: User.can_edit_message?(socket.assigns[:user], msg)} )) {:noreply, socket} end # do not send broadcasted `"user_joined"` events if this socket's user # is ignoring the user who joined. def handle_out("user_joined", msg, socket) do unless User.ignoring?(socket.assigns[:user], msg.user_id) do push(socket, "user_joined", msg) end {:noreply, socket} end ``` ## [](Phoenix.Channel.html#module-broadcasting-to-an-external-topic)Broadcasting to an external topic In some cases, you will want to broadcast messages without the context of a `socket`. This could be for broadcasting from within your channel to an external topic, or broadcasting from elsewhere in your application like a controller or another process. Such can be done via your endpoint: ``` # within channel def handle_in("new_msg", %{"uid" => uid, "body" => body}, socket) do ... broadcast_from!(socket, "new_msg", %{uid: uid, body: body}) MyAppWeb.Endpoint.broadcast_from!(self(), "room:superadmin", "new_msg", %{uid: uid, body: body}) {:noreply, socket} end # within controller def create(conn, params) do ... MyAppWeb.Endpoint.broadcast!("room:" <> rid, "new_msg", %{uid: uid, body: body}) MyAppWeb.Endpoint.broadcast!("room:superadmin", "new_msg", %{uid: uid, body: body}) redirect(conn, to: "/") end ``` ## [](Phoenix.Channel.html#module-terminate)Terminate On termination, the channel callback `terminate/2` will be invoked with the error reason and the socket. If we are terminating because the client left, the reason will be `{:shutdown, :left}`. Similarly, if we are terminating because the client connection was closed, the reason will be `{:shutdown, :closed}`. If any of the callbacks return a `:stop` tuple, it will also trigger terminate with the reason given in the tuple. `terminate/2`, however, won't be invoked in case of errors nor in case of exits. This is the same behaviour as you find in Elixir abstractions like [`GenServer`](../elixir/GenServer.html) and others. Similar to [`GenServer`](../elixir/GenServer.html), it would also be possible to `:trap_exit` to guarantee that `terminate/2` is invoked. This practice is not encouraged though. Generally speaking, if you want to clean something up, it is better to monitor your channel process and do the clean up from another process. All channel callbacks, including `join/3`, are called from within the channel process. Therefore, `self()` in any of them returns the PID to be monitored. ## [](Phoenix.Channel.html#module-exit-reasons-when-stopping-a-channel)Exit reasons when stopping a channel When the channel callbacks return a `:stop` tuple, such as: ``` {:stop, :shutdown, socket} {:stop, {:error, :enoent}, socket} ``` the second argument is the exit reason, which follows the same behaviour as standard [`GenServer`](../elixir/GenServer.html) exits. You have three options to choose from when shutting down a channel: - `:normal` - in such cases, the exit won't be logged and linked processes do not exit - `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be logged and linked processes exit with the same reason unless they're trapping exits - any other term - in such cases, the exit will be logged and linked processes exit with the same reason unless they're trapping exits ## [](Phoenix.Channel.html#module-subscribing-to-external-topics)Subscribing to external topics Sometimes you may need to programmatically subscribe a socket to external topics in addition to the internal `socket.topic`. For example, imagine you have a bidding system where a remote client dynamically sets preferences on products they want to receive bidding notifications on. Instead of requiring a unique channel process and topic per preference, a more efficient and simple approach would be to subscribe a single channel to relevant notifications via your endpoint. For example: ``` defmodule MyAppWeb.Endpoint.NotificationChannel do use Phoenix.Channel def join("notification:" <> user_id, %{"ids" => ids}, socket) do topics = for product_id <- ids, do: "product:#{product_id}" {:ok, socket |> assign(:topics, []) |> put_new_topics(topics)} end def handle_in("watch", %{"product_id" => id}, socket) do {:reply, :ok, put_new_topics(socket, ["product:#{id}"])} end def handle_in("unwatch", %{"product_id" => id}, socket) do {:reply, :ok, MyAppWeb.Endpoint.unsubscribe("product:#{id}")} end defp put_new_topics(socket, topics) do Enum.reduce(topics, socket, fn topic, acc -> topics = acc.assigns.topics if topic in topics do acc else :ok = MyAppWeb.Endpoint.subscribe(topic) assign(acc, :topics, [topic | topics]) end end) end end ``` Note: the caller must be responsible for preventing duplicate subscriptions. After calling `subscribe/1` from your endpoint, the same flow applies to handling regular Elixir messages within your channel. Most often, you'll simply relay the `%Phoenix.Socket.Broadcast{}` event and payload: ``` alias Phoenix.Socket.Broadcast def handle_info(%Broadcast{topic: _, event: event, payload: payload}, socket) do push(socket, event, payload) {:noreply, socket} end ``` ## [](Phoenix.Channel.html#module-hibernation)Hibernation From Erlang/OTP 20, channels automatically hibernate to save memory after 15\_000 milliseconds of inactivity. This can be customized by passing the `:hibernate_after` option to `use Phoenix.Channel`: ``` use Phoenix.Channel, hibernate_after: 60_000 ``` You can also set it to `:infinity` to fully disable it. ## [](Phoenix.Channel.html#module-shutdown)Shutdown You can configure the shutdown behavior of each channel used when your application is shutting down by setting the `:shutdown` value on use: ``` use Phoenix.Channel, shutdown: 5_000 ``` It defaults to 5\_000. The supported values are described under the in the [`Supervisor`](../elixir/Supervisor.html) module docs. ## [](Phoenix.Channel.html#module-logging)Logging By default, channel `"join"` and `"handle_in"` events are logged, using the level `:info` and `:debug`, respectively. You can change the level used for each event, or disable logs, per event type by setting the `:log_join` and `:log_handle_in` options when using [`Phoenix.Channel`](Phoenix.Channel.html). For example, the following configuration logs join events as `:info`, but disables logging for incoming events: ``` use Phoenix.Channel, log_join: :info, log_handle_in: false ``` Note that changing an event type's level doesn't affect what is logged, unless you set it to `false`, it affects the associated level. # [](Phoenix.Channel.html#summary)Summary ## [Types](Phoenix.Channel.html#types) [payload()](Phoenix.Channel.html#t:payload/0) [reply()](Phoenix.Channel.html#t:reply/0) [socket\_ref()](Phoenix.Channel.html#t:socket_ref/0) ## [Callbacks](Phoenix.Channel.html#callbacks) [code\_change(old\_vsn, t, extra)](Phoenix.Channel.html#c:code_change/3) [handle\_call(msg, from, socket)](Phoenix.Channel.html#c:handle_call/3) Handle regular GenServer call messages. [handle\_cast(msg, socket)](Phoenix.Channel.html#c:handle_cast/2) Handle regular GenServer cast messages. [handle\_in(event, payload, socket)](Phoenix.Channel.html#c:handle_in/3) Handle incoming `event`s. [handle\_info(msg, socket)](Phoenix.Channel.html#c:handle_info/2) Handle regular Elixir process messages. [handle\_out(event, payload, socket)](Phoenix.Channel.html#c:handle_out/3) Intercepts outgoing `event`s. [join(topic, payload, socket)](Phoenix.Channel.html#c:join/3) Handle channel joins by `topic`. [terminate(reason, t)](Phoenix.Channel.html#c:terminate/2) Invoked when the channel process is about to exit. ## [Functions](Phoenix.Channel.html#functions) [broadcast(socket, event, message)](Phoenix.Channel.html#broadcast/3) Broadcast an event to all subscribers of the socket topic. [broadcast!(socket, event, message)](Phoenix.Channel.html#broadcast!/3) Same as [`broadcast/3`](Phoenix.Channel.html#broadcast/3), but raises if broadcast fails. [broadcast\_from(socket, event, message)](Phoenix.Channel.html#broadcast_from/3) Broadcast event from pid to all subscribers of the socket topic. [broadcast\_from!(socket, event, message)](Phoenix.Channel.html#broadcast_from!/3) Same as [`broadcast_from/3`](Phoenix.Channel.html#broadcast_from/3), but raises if broadcast fails. [intercept(events)](Phoenix.Channel.html#intercept/1) Defines which Channel events to intercept for `handle_out/3` callbacks. [push(socket, event, message)](Phoenix.Channel.html#push/3) Sends an event directly to the connected client without requiring a prior message from the client. [reply(socket\_ref, status)](Phoenix.Channel.html#reply/2) Replies asynchronously to a socket push. [socket\_ref(socket)](Phoenix.Channel.html#socket_ref/1) Generates a `socket_ref` for an async reply. # [](Phoenix.Channel.html#types)Types [](Phoenix.Channel.html#t:payload/0) # payload() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L352) ``` @type payload() :: map() | term() | {:binary, binary()} ``` [](Phoenix.Channel.html#t:reply/0) # reply() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L353) ``` @type reply() :: status :: atom() | {status :: atom(), response :: payload()} ``` [](Phoenix.Channel.html#t:socket_ref/0) # socket\_ref() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L354) ``` @type socket_ref() :: {transport_pid :: Pid, serializer :: module(), topic :: binary(), ref :: binary(), join_ref :: binary()} ``` # [](Phoenix.Channel.html#callbacks)Callbacks [](Phoenix.Channel.html#c:code_change/3) # code\_change(old\_vsn, t, extra) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L439)(optional) ``` @callback code_change(old_vsn, Phoenix.Socket.t(), extra :: term()) :: {:ok, Phoenix.Socket.t()} | {:error, reason :: term()} when old_vsn: term() | {:down, term()} ``` [](Phoenix.Channel.html#c:handle_call/3) # handle\_call(msg, from, socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L424)(optional) ``` @callback handle_call( msg :: term(), from :: {pid(), tag :: term()}, socket :: Phoenix.Socket.t() ) :: {:reply, response :: term(), Phoenix.Socket.t()} | {:noreply, Phoenix.Socket.t()} | {:stop, reason :: term(), Phoenix.Socket.t()} ``` Handle regular GenServer call messages. See [`GenServer.handle_call/3`](../elixir/GenServer.html#c:handle_call/3). [](Phoenix.Channel.html#c:handle_cast/2) # handle\_cast(msg, socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L434)(optional) ``` @callback handle_cast(msg :: term(), socket :: Phoenix.Socket.t()) :: {:noreply, Phoenix.Socket.t()} | {:stop, reason :: term(), Phoenix.Socket.t()} ``` Handle regular GenServer cast messages. See [`GenServer.handle_cast/2`](../elixir/GenServer.html#c:handle_cast/2). [](Phoenix.Channel.html#c:handle_in/3) # handle\_in(event, payload, socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L393)(optional) ``` @callback handle_in( event :: String.t(), payload :: payload(), socket :: Phoenix.Socket.t() ) :: {:noreply, Phoenix.Socket.t()} | {:noreply, Phoenix.Socket.t(), timeout() | :hibernate} | {:reply, reply(), Phoenix.Socket.t()} | {:stop, reason :: term(), Phoenix.Socket.t()} | {:stop, reason :: term(), reply(), Phoenix.Socket.t()} ``` Handle incoming `event`s. Payloads are serialized before sending with the configured serializer. ## [](Phoenix.Channel.html#c:handle_in/3-example)Example ``` def handle_in("ping", payload, socket) do {:reply, {:ok, payload}, socket} end ``` [](Phoenix.Channel.html#c:handle_info/2) # handle\_info(msg, socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L415)(optional) ``` @callback handle_info(msg :: term(), socket :: Phoenix.Socket.t()) :: {:noreply, Phoenix.Socket.t()} | {:stop, reason :: term(), Phoenix.Socket.t()} ``` Handle regular Elixir process messages. See [`GenServer.handle_info/2`](../elixir/GenServer.html#c:handle_info/2). [](Phoenix.Channel.html#c:handle_out/3) # handle\_out(event, payload, socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L405)(optional) ``` @callback handle_out( event :: String.t(), payload :: payload(), socket :: Phoenix.Socket.t() ) :: {:noreply, Phoenix.Socket.t()} | {:noreply, Phoenix.Socket.t(), timeout() | :hibernate} | {:stop, reason :: term(), Phoenix.Socket.t()} ``` Intercepts outgoing `event`s. See [`intercept/1`](Phoenix.Channel.html#intercept/1). [](Phoenix.Channel.html#c:join/3) # join(topic, payload, socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L377) ``` @callback join(topic :: binary(), payload :: payload(), socket :: Phoenix.Socket.t()) :: {:ok, Phoenix.Socket.t()} | {:ok, reply :: payload(), Phoenix.Socket.t()} | {:error, reason :: map()} ``` Handle channel joins by `topic`. To authorize a socket, return `{:ok, socket}` or `{:ok, reply, socket}`. To refuse authorization, return `{:error, reason}`. Payloads are serialized before sending with the configured serializer. ## [](Phoenix.Channel.html#c:join/3-example)Example ``` def join("room:lobby", payload, socket) do if authorized?(payload) do {:ok, socket} else {:error, %{reason: "unauthorized"}} end end ``` [](Phoenix.Channel.html#c:terminate/2) # terminate(reason, t) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L449)(optional) ``` @callback terminate( reason :: :normal | :shutdown | {:shutdown, :left | :closed | term()}, Phoenix.Socket.t() ) :: term() ``` Invoked when the channel process is about to exit. See [`GenServer.terminate/2`](../elixir/GenServer.html#c:terminate/2). # [](Phoenix.Channel.html#functions)Functions [](Phoenix.Channel.html#broadcast/3) # broadcast(socket, event, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L572) Broadcast an event to all subscribers of the socket topic. The event's message must be a serializable map or a tagged `{:binary, data}` tuple where `data` is binary data. ## [](Phoenix.Channel.html#broadcast/3-examples)Examples ``` iex> broadcast(socket, "new_message", %{id: 1, content: "hello"}) :ok iex> broadcast(socket, "new_message", {:binary, "hello"}) :ok ``` [](Phoenix.Channel.html#broadcast!/3) # broadcast!(socket, event, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L580) Same as [`broadcast/3`](Phoenix.Channel.html#broadcast/3), but raises if broadcast fails. [](Phoenix.Channel.html#broadcast_from/3) # broadcast\_from(socket, event, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L601) Broadcast event from pid to all subscribers of the socket topic. The channel that owns the socket will not receive the published message. The event's message must be a serializable map or a tagged `{:binary, data}` tuple where `data` is binary data. ## [](Phoenix.Channel.html#broadcast_from/3-examples)Examples ``` iex> broadcast_from(socket, "new_message", %{id: 1, content: "hello"}) :ok iex> broadcast_from(socket, "new_message", {:binary, "hello"}) :ok ``` [](Phoenix.Channel.html#broadcast_from!/3) # broadcast\_from!(socket, event, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L611) Same as [`broadcast_from/3`](Phoenix.Channel.html#broadcast_from/3), but raises if broadcast fails. [](Phoenix.Channel.html#intercept/1) # intercept(events) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L535)(macro) Defines which Channel events to intercept for `handle_out/3` callbacks. By default, broadcasted events are pushed directly to the client, but intercepting events gives your channel a chance to customize the event for the client to append extra information or filter the message from being delivered. *Note*: intercepting events can introduce significantly more overhead if a large number of subscribers must customize a message since the broadcast will be encoded N times instead of a single shared encoding across all subscribers. ## [](Phoenix.Channel.html#intercept/1-examples)Examples ``` intercept ["new_msg"] def handle_out("new_msg", payload, socket) do push(socket, "new_msg", Map.merge(payload, is_editable: User.can_edit_message?(socket.assigns[:user], payload) )) {:noreply, socket} end ``` `handle_out/3` callbacks must return one of: ``` {:noreply, Socket.t} | {:noreply, Socket.t, timeout | :hibernate} | {:stop, reason :: term, Socket.t} ``` [](Phoenix.Channel.html#push/3) # push(socket, event, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L641) Sends an event directly to the connected client without requiring a prior message from the client. The event's message must be a serializable map or a tagged `{:binary, data}` tuple where `data` is binary data. Note that unlike some in client libraries, this server-side [`push/3`](Phoenix.Channel.html#push/3) does not return a reference. If you need to get a reply from the client and to correlate that reply with the message you pushed, you'll need to include a unique identifier in the message, track it in the Channel's state, have the client include it in its reply, and examine the ref when the reply comes to `handle_in/3`. ## [](Phoenix.Channel.html#push/3-examples)Examples ``` iex> push(socket, "new_message", %{id: 1, content: "hello"}) :ok iex> push(socket, "new_message", {:binary, "hello"}) :ok ``` [](Phoenix.Channel.html#reply/2) # reply(socket\_ref, status) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L687) ``` @spec reply(socket_ref(), reply()) :: :ok ``` Replies asynchronously to a socket push. The usual way of replying to a client's message is to return a tuple from `handle_in/3` like: ``` {:reply, {status, payload}, socket} ``` But sometimes you need to reply to a push asynchronously - that is, after your `handle_in/3` callback completes. For example, you might need to perform work in another process and reply when it's finished. You can do this by generating a reference to the socket with [`socket_ref/1`](Phoenix.Channel.html#socket_ref/1) and calling [`reply/2`](Phoenix.Channel.html#reply/2) with that ref when you're ready to reply. *Note*: A `socket_ref` is required so the `socket` itself is not leaked outside the channel. The `socket` holds information such as assigns and transport configuration, so it's important to not copy this information outside of the channel that owns it. Technically, [`reply/2`](Phoenix.Channel.html#reply/2) will allow you to reply multiple times to the same client message, and each reply will include the client message `ref`. But the client may expect only one reply; in that case, [`push/3`](Phoenix.Channel.html#push/3) would be preferable for the additional messages. Payloads are serialized before sending with the configured serializer. ## [](Phoenix.Channel.html#reply/2-examples)Examples ``` def handle_in("work", payload, socket) do Worker.perform(payload, socket_ref(socket)) {:noreply, socket} end def handle_info({:work_complete, result, ref}, socket) do reply(ref, {:ok, result}) {:noreply, socket} end ``` [](Phoenix.Channel.html#socket_ref/1) # socket\_ref(socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/channel.ex#L701) ``` @spec socket_ref(Phoenix.Socket.t()) :: socket_ref() ``` Generates a `socket_ref` for an async reply. See [`reply/2`](Phoenix.Channel.html#reply/2) for example usage. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.ChannelTest.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L1 "View Source") Phoenix.ChannelTest (Phoenix v1.7.18) Conveniences for testing Phoenix channels. In channel tests, we interact with channels via process communication, sending and receiving messages. It is also common to subscribe to the same topic the channel subscribes to, allowing us to assert if a given message was broadcast or not. ## [](Phoenix.ChannelTest.html#module-channel-testing)Channel testing To get started, define the module attribute `@endpoint` in your test case pointing to your application endpoint. Then you can directly create a socket and [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4) topics and channels: ``` {:ok, _, socket} = socket(UserSocket, "user:id", %{some_assigns: 1}) |> subscribe_and_join(RoomChannel, "room:lobby", %{"id" => 3}) ``` You usually want to set the same ID and assigns your `UserSocket.connect/3` callback would set. Alternatively, you can use the [`connect/3`](Phoenix.ChannelTest.html#connect/3) helper to call your `UserSocket.connect/3` callback and initialize the socket with the socket id: ``` {:ok, socket} = connect(UserSocket, %{"some" => "params"}, %{}) {:ok, _, socket} = subscribe_and_join(socket, "room:lobby", %{"id" => 3}) ``` Once called, [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4) will subscribe the current test process to the "room:lobby" topic and start a channel in another process. It returns `{:ok, reply, socket}` or `{:error, reply}`. Now, in the same way the channel has a socket representing communication it will push to the client. Our test has a socket representing communication to be pushed to the server. For example, we can use the [`push/3`](Phoenix.ChannelTest.html#push/3) function in the test to push messages to the channel (it will invoke `handle_in/3`): ``` push(socket, "my_event", %{"some" => "data"}) ``` Similarly, we can broadcast messages from the test itself on the topic that both test and channel are subscribed to, triggering `handle_out/3` on the channel: ``` broadcast_from(socket, "my_event", %{"some" => "data"}) ``` > Note only [`broadcast_from/3`](Phoenix.ChannelTest.html#broadcast_from/3) and [`broadcast_from!/3`](Phoenix.ChannelTest.html#broadcast_from!/3) are available in tests to avoid broadcast messages to be resent to the test process. While the functions above are pushing data to the channel (server) we can use [`assert_push/3`](Phoenix.ChannelTest.html#assert_push/3) to verify the channel pushed a message to the client: ``` assert_push "my_event", %{"some" => "data"} ``` Or even assert something was broadcast into pubsub: ``` assert_broadcast "my_event", %{"some" => "data"} ``` Finally, every time a message is pushed to the channel, a reference is returned. We can use this reference to assert a particular reply was sent from the server: ``` ref = push(socket, "counter", %{}) assert_reply ref, :ok, %{"counter" => 1} ``` ## [](Phoenix.ChannelTest.html#module-checking-side-effects)Checking side-effects Often one may want to do side-effects inside channels, like writing to the database, and verify those side-effects during their tests. Imagine the following `handle_in/3` inside a channel: ``` def handle_in("publish", %{"id" => id}, socket) do Repo.get!(Post, id) |> Post.publish() |> Repo.update!() {:noreply, socket} end ``` Because the whole communication is asynchronous, the following test would be very brittle: ``` push(socket, "publish", %{"id" => 3}) assert Repo.get_by(Post, id: 3, published: true) ``` The issue is that we have no guarantees the channel has done processing our message after calling [`push/3`](Phoenix.ChannelTest.html#push/3). The best solution is to assert the channel sent us a reply before doing any other assertion. First change the channel to send replies: ``` def handle_in("publish", %{"id" => id}, socket) do Repo.get!(Post, id) |> Post.publish() |> Repo.update!() {:reply, :ok, socket} end ``` Then expect them in the test: ``` ref = push(socket, "publish", %{"id" => 3}) assert_reply ref, :ok assert Repo.get_by(Post, id: 3, published: true) ``` ## [](Phoenix.ChannelTest.html#module-leave-and-close)Leave and close This module also provides functions to simulate leaving and closing a channel. Once you leave or close a channel, because the channel is linked to the test process on join, it will crash the test process: ``` leave(socket) ** (EXIT from #PID<...>) {:shutdown, :leave} ``` You can avoid this by unlinking the channel process in the test: ``` Process.unlink(socket.channel_pid) ``` Notice [`leave/1`](Phoenix.ChannelTest.html#leave/1) is async, so it will also return a reference which you can use to check for a reply: ``` ref = leave(socket) assert_reply ref, :ok ``` On the other hand, close is always sync and it will return only after the channel process is guaranteed to have been terminated: ``` :ok = close(socket) ``` This mimics the behaviour existing in clients. To assert that your channel closes or errors asynchronously, you can monitor the channel process with the tools provided by Elixir, and wait for the `:DOWN` message. Imagine an implementation of the `handle_info/2` function that closes the channel when it receives `:some_message`: ``` def handle_info(:some_message, socket) do {:stop, :normal, socket} end ``` In your test, you can assert that the close happened by: ``` Process.monitor(socket.channel_pid) send(socket.channel_pid, :some_message) assert_receive {:DOWN, _, _, _, :normal} ``` # [](Phoenix.ChannelTest.html#summary)Summary ## [Functions](Phoenix.ChannelTest.html#functions) [assert\_broadcast(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.ChannelTest.html#assert_broadcast/3) Asserts the channel has broadcast a message within `timeout`. [assert\_push(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.ChannelTest.html#assert_push/3) Asserts the channel has pushed a message back to the client with the given event and payload within `timeout`. [assert\_reply(ref, status, payload \\\\ Macro.escape(%{}), timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.ChannelTest.html#assert_reply/4) Asserts the channel has replied to the given message within `timeout`. [broadcast\_from(socket, event, message)](Phoenix.ChannelTest.html#broadcast_from/3) Broadcast event from pid to all subscribers of the socket topic. [broadcast\_from!(socket, event, message)](Phoenix.ChannelTest.html#broadcast_from!/3) Same as [`broadcast_from/3`](Phoenix.ChannelTest.html#broadcast_from/3), but raises if broadcast fails. [close(socket, timeout \\\\ 5000)](Phoenix.ChannelTest.html#close/2) Emulates the client closing the socket. [connect(handler, params, options \\\\ quote do \[\] end)](Phoenix.ChannelTest.html#connect/3) Initiates a transport connection for the socket handler. [join(socket, topic)](Phoenix.ChannelTest.html#join/2) See [`join/4`](Phoenix.ChannelTest.html#join/4). [join(socket, topic, payload)](Phoenix.ChannelTest.html#join/3) See [`join/4`](Phoenix.ChannelTest.html#join/4). [join(socket, channel, topic, payload \\\\ %{})](Phoenix.ChannelTest.html#join/4) Joins the channel under the given topic and payload. [leave(socket)](Phoenix.ChannelTest.html#leave/1) Emulates the client leaving the channel. [push(socket, event, payload \\\\ %{})](Phoenix.ChannelTest.html#push/3) Pushes a message into the channel. [refute\_broadcast(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout))](Phoenix.ChannelTest.html#refute_broadcast/3) Asserts the channel has not broadcast a message within `timeout`. [refute\_push(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout))](Phoenix.ChannelTest.html#refute_push/3) Asserts the channel has not pushed a message to the client matching the given event and payload within `timeout`. [refute\_reply(ref, status, payload \\\\ Macro.escape(%{}), timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout))](Phoenix.ChannelTest.html#refute_reply/4) Asserts the channel has not replied with a matching payload within `timeout`. [socket(socket\_module)](Phoenix.ChannelTest.html#socket/1) Builds a socket for the given `socket_module`. [socket(socket\_module, socket\_id, socket\_assigns, options \\\\ \[\])](Phoenix.ChannelTest.html#socket/4) Builds a socket for the given `socket_module` with given id and assigns. [subscribe\_and\_join(socket, topic)](Phoenix.ChannelTest.html#subscribe_and_join/2) See [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4). [subscribe\_and\_join(socket, topic, payload)](Phoenix.ChannelTest.html#subscribe_and_join/3) See [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4). [subscribe\_and\_join(socket, channel, topic, payload \\\\ %{})](Phoenix.ChannelTest.html#subscribe_and_join/4) Subscribes to the given topic and joins the channel under the given topic and payload. [subscribe\_and\_join!(socket, topic)](Phoenix.ChannelTest.html#subscribe_and_join!/2) See [`subscribe_and_join!/4`](Phoenix.ChannelTest.html#subscribe_and_join!/4). [subscribe\_and\_join!(socket, topic, payload)](Phoenix.ChannelTest.html#subscribe_and_join!/3) See [`subscribe_and_join!/4`](Phoenix.ChannelTest.html#subscribe_and_join!/4). [subscribe\_and\_join!(socket, channel, topic, payload \\\\ %{})](Phoenix.ChannelTest.html#subscribe_and_join!/4) Same as [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4), but returns either the socket or throws an error. # [](Phoenix.ChannelTest.html#functions)Functions [](Phoenix.ChannelTest.html#assert_broadcast/3) # assert\_broadcast(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L646)(macro) Asserts the channel has broadcast a message within `timeout`. Before asserting anything was broadcast, we must first subscribe to the topic of the channel in the test process: ``` @endpoint.subscribe("foo:ok") ``` Now we can match on event and payload as patterns: ``` assert_broadcast "some_event", %{"data" => _} ``` In the assertion above, we don't particularly care about the data being sent, as long as something was sent. The timeout is in milliseconds and defaults to the `:assert_receive_timeout` set on the `:ex_unit` application (which defaults to 100ms). [](Phoenix.ChannelTest.html#assert_push/3) # assert\_push(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L553)(macro) Asserts the channel has pushed a message back to the client with the given event and payload within `timeout`. Notice event and payload are patterns. This means one can write: ``` assert_push "some_event", %{"data" => _} ``` In the assertion above, we don't particularly care about the data being sent, as long as something was sent. The timeout is in milliseconds and defaults to the `:assert_receive_timeout` set on the `:ex_unit` application (which defaults to 100ms). **NOTE:** Because event and payload are patterns, they will be matched. This means that if you wish to assert that the received payload is equivalent to an existing variable, you need to pin the variable in the assertion expression. Good: ``` expected_payload = %{foo: "bar"} assert_push "some_event", ^expected_payload ``` Bad: ``` expected_payload = %{foo: "bar"} assert_push "some_event", expected_payload # The code above does not assert the payload matches the described map. ``` [](Phoenix.ChannelTest.html#assert_reply/4) # assert\_reply(ref, status, payload \\\\ Macro.escape(%{}), timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L596)(macro) Asserts the channel has replied to the given message within `timeout`. Notice status and payload are patterns. This means one can write: ``` ref = push(channel, "some_event") assert_reply ref, :ok, %{"data" => _} ``` In the assertion above, we don't particularly care about the data being sent, as long as something was replied. The timeout is in milliseconds and defaults to the `:assert_receive_timeout` set on the `:ex_unit` application (which defaults to 100ms). [](Phoenix.ChannelTest.html#broadcast_from/3) # broadcast\_from(socket, event, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L509) Broadcast event from pid to all subscribers of the socket topic. The test process will not receive the published message. This triggers the `handle_out/3` callback in the channel. ## [](Phoenix.ChannelTest.html#broadcast_from/3-examples)Examples ``` iex> broadcast_from(socket, "new_message", %{id: 1, content: "hello"}) :ok ``` [](Phoenix.ChannelTest.html#broadcast_from!/3) # broadcast\_from!(socket, event, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L517) Same as [`broadcast_from/3`](Phoenix.ChannelTest.html#broadcast_from/3), but raises if broadcast fails. [](Phoenix.ChannelTest.html#close/2) # close(socket, timeout \\\\ 5000) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L493) Emulates the client closing the socket. Closing socket is synchronous and has a default timeout of 5000 milliseconds. [](Phoenix.ChannelTest.html#connect/3) # connect(handler, params, options \\\\ quote do \[] end) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L310)(macro) Initiates a transport connection for the socket handler. Useful for testing UserSocket authentication. Returns the result of the handler's [`connect/3`](Phoenix.ChannelTest.html#connect/3) callback. [](Phoenix.ChannelTest.html#join/2) # join(socket, topic) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L411) See [`join/4`](Phoenix.ChannelTest.html#join/4). [](Phoenix.ChannelTest.html#join/3) # join(socket, topic, payload) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L416) See [`join/4`](Phoenix.ChannelTest.html#join/4). [](Phoenix.ChannelTest.html#join/4) # join(socket, channel, topic, payload \\\\ %{}) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L428) Joins the channel under the given topic and payload. The given channel is joined in a separate process which is linked to the test process. It returns `{:ok, reply, socket}` or `{:error, reply}`. [](Phoenix.ChannelTest.html#leave/1) # leave(socket) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L483) ``` @spec leave(Phoenix.Socket.t()) :: reference() ``` Emulates the client leaving the channel. [](Phoenix.ChannelTest.html#push/3) # push(socket, event, payload \\\\ %{}) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L472) ``` @spec push(Phoenix.Socket.t(), String.t(), map()) :: reference() ``` Pushes a message into the channel. The triggers the `handle_in/3` callback in the channel. ## [](Phoenix.ChannelTest.html#push/3-examples)Examples ``` iex> push(socket, "new_message", %{id: 1, content: "hello"}) reference ``` [](Phoenix.ChannelTest.html#refute_broadcast/3) # refute\_broadcast(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L664)(macro) Asserts the channel has not broadcast a message within `timeout`. Like `assert_broadcast`, the event and payload are patterns. The timeout is in milliseconds and defaults to the `:refute_receive_timeout` set on the `:ex_unit` application (which defaults to 100ms). Keep in mind this macro will block the test by the timeout value, so use it only when necessary as overuse will certainly slow down your test suite. [](Phoenix.ChannelTest.html#refute_push/3) # refute\_push(event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L573)(macro) Asserts the channel has not pushed a message to the client matching the given event and payload within `timeout`. Like `assert_push`, the event and payload are patterns. The timeout is in milliseconds and defaults to the `:refute_receive_timeout` set on the `:ex_unit` application (which defaults to 100ms). Keep in mind this macro will block the test by the timeout value, so use it only when necessary as overuse will certainly slow down your test suite. [](Phoenix.ChannelTest.html#refute_reply/4) # refute\_reply(ref, status, payload \\\\ Macro.escape(%{}), timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L618)(macro) Asserts the channel has not replied with a matching payload within `timeout`. Like `assert_reply`, the event and payload are patterns. The timeout is in milliseconds and defaults to the `:refute_receive_timeout` set on the `:ex_unit` application (which defaults to 100ms). Keep in mind this macro will block the test by the timeout value, so use it only when necessary as overuse will certainly slow down your test suite. [](Phoenix.ChannelTest.html#socket/1) # socket(socket\_module) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L208)(macro) Builds a socket for the given `socket_module`. The socket is then used to subscribe and join channels. Use this function when you want to create a blank socket to pass to functions like `UserSocket.connect/3`. Otherwise, use [`socket/4`](Phoenix.ChannelTest.html#socket/4) if you want to build a socket with existing id and assigns. ## [](Phoenix.ChannelTest.html#socket/1-examples)Examples ``` socket(MyApp.UserSocket) ``` [](Phoenix.ChannelTest.html#socket/4) # socket(socket\_module, socket\_id, socket\_assigns, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L235)(macro) Builds a socket for the given `socket_module` with given id and assigns. ## [](Phoenix.ChannelTest.html#socket/4-examples)Examples ``` socket(MyApp.UserSocket, "user_id", %{some: :assign}) ``` If you need to access the socket in another process than the test process, you can give the `pid` of the test process in the 4th argument. ## [](Phoenix.ChannelTest.html#socket/4-examples-1)Examples ``` test "connect in a task" do pid = self() task = Task.async(fn -> socket = socket(MyApp.UserSocket, "user_id", %{some: :assign}, test_process: pid) broadcast_from!(socket, "default", %{"foo" => "bar"}) assert_push "default", %{"foo" => "bar"} end) Task.await(task) end ``` [](Phoenix.ChannelTest.html#subscribe_and_join/2) # subscribe\_and\_join(socket, topic) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L378) See [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4). [](Phoenix.ChannelTest.html#subscribe_and_join/3) # subscribe\_and\_join(socket, topic, payload) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L383) See [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4). [](Phoenix.ChannelTest.html#subscribe_and_join/4) # subscribe\_and\_join(socket, channel, topic, payload \\\\ %{}) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L404) Subscribes to the given topic and joins the channel under the given topic and payload. By subscribing to the topic, we can use [`assert_broadcast/3`](Phoenix.ChannelTest.html#assert_broadcast/3) to verify a message has been sent through the pubsub layer. By joining the channel, we can interact with it directly. The given channel is joined in a separate process which is linked to the test process. If no channel module is provided, the socket's handler is used to lookup the matching channel for the given topic. It returns `{:ok, reply, socket}` or `{:error, reply}`. [](Phoenix.ChannelTest.html#subscribe_and_join!/2) # subscribe\_and\_join!(socket, topic) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L352) See [`subscribe_and_join!/4`](Phoenix.ChannelTest.html#subscribe_and_join!/4). [](Phoenix.ChannelTest.html#subscribe_and_join!/3) # subscribe\_and\_join!(socket, topic, payload) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L357) See [`subscribe_and_join!/4`](Phoenix.ChannelTest.html#subscribe_and_join!/4). [](Phoenix.ChannelTest.html#subscribe_and_join!/4) # subscribe\_and\_join!(socket, channel, topic, payload \\\\ %{}) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/channel_test.ex#L369) Same as [`subscribe_and_join/4`](Phoenix.ChannelTest.html#subscribe_and_join/4), but returns either the socket or throws an error. This is helpful when you are not testing joining the channel and just need the socket. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.CodeReloader.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/code_reloader.ex#L1 "View Source") Phoenix.CodeReloader (Phoenix v1.7.18) A plug and module to handle automatic code reloading. To avoid race conditions, all code reloads are funneled through a sequential call operation. # [](Phoenix.CodeReloader.html#summary)Summary ## [Functions](Phoenix.CodeReloader.html#functions) [call(conn, opts)](Phoenix.CodeReloader.html#call/2) API used by Plug to invoke the code reloader on every request. [init(opts)](Phoenix.CodeReloader.html#init/1) API used by Plug to start the code reloader. [reload(endpoint, opts \\\\ \[\])](Phoenix.CodeReloader.html#reload/2) Reloads code for the current Mix project by invoking the `:reloadable_compilers` on the list of `:reloadable_apps`. [reload!(endpoint, opts)](Phoenix.CodeReloader.html#reload!/2) Same as [`reload/1`](Phoenix.CodeReloader.html#reload/1) but it will raise if Mix is not available. [sync()](Phoenix.CodeReloader.html#sync/0) Synchronizes with the code server if it is alive. # [](Phoenix.CodeReloader.html#functions)Functions [](Phoenix.CodeReloader.html#call/2) # call(conn, opts) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/code_reloader.ex#L84) API used by Plug to invoke the code reloader on every request. [](Phoenix.CodeReloader.html#init/1) # init(opts) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/code_reloader.ex#L77) API used by Plug to start the code reloader. [](Phoenix.CodeReloader.html#reload/2) # reload(endpoint, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/code_reloader.ex#L42) ``` @spec reload( module(), keyword() ) :: :ok | {:error, binary()} ``` Reloads code for the current Mix project by invoking the `:reloadable_compilers` on the list of `:reloadable_apps`. This is configured in your application environment like: ``` config :your_app, YourAppWeb.Endpoint, reloadable_compilers: [:gettext, :elixir], reloadable_apps: [:ui, :backend] ``` Keep in mind `:reloadable_compilers` must be a subset of the `:compilers` specified in `project/0` in your `mix.exs`. The `:reloadable_apps` defaults to `nil`. In such case default behaviour is to reload the current project if it consists of a single app, or all applications within an umbrella project. You can set `:reloadable_apps` to a subset of default applications to reload only some of them, an empty list - to effectively disable the code reloader, or include external applications from library dependencies. This function is a no-op and returns `:ok` if Mix is not available. ## [](Phoenix.CodeReloader.html#reload/2-options)Options - `:reloadable_args` - additional CLI args to pass to the compiler tasks. Defaults to `["--no-all-warnings"]` so only warnings related to the files being compiled are printed [](Phoenix.CodeReloader.html#reload!/2) # reload!(endpoint, opts) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/code_reloader.ex#L50) ``` @spec reload!( module(), keyword() ) :: :ok | {:error, binary()} ``` Same as [`reload/1`](Phoenix.CodeReloader.html#reload/1) but it will raise if Mix is not available. [](Phoenix.CodeReloader.html#sync/0) # sync() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/code_reloader.ex#L58) ``` @spec sync() :: :ok ``` Synchronizes with the code server if it is alive. It returns `:ok`. If it is not running, it also returns `:ok`. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.ConnTest.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L1 "View Source") Phoenix.ConnTest (Phoenix v1.7.18) Conveniences for testing Phoenix endpoints and connection related helpers. You likely want to use this module or make it part of your [`ExUnit.CaseTemplate`](../ex_unit/ExUnit.CaseTemplate.html). Once used, this module automatically imports all functions defined here as well as the functions in [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html). ## [](Phoenix.ConnTest.html#module-endpoint-testing)Endpoint testing [`Phoenix.ConnTest`](Phoenix.ConnTest.html) typically works against endpoints. That's the preferred way to test anything that your router dispatches to: ``` @endpoint MyAppWeb.Endpoint test "says welcome on the home page" do conn = get(build_conn(), "/") assert conn.resp_body =~ "Welcome!" end test "logs in" do conn = post(build_conn(), "/login", [username: "john", password: "doe"]) assert conn.resp_body =~ "Logged in!" end ``` The `@endpoint` module attribute contains the endpoint under testing, most commonly your application endpoint itself. If you are using the MyApp.ConnCase generated by Phoenix, it is automatically set for you. As in your router and controllers, the connection is the main abstraction in testing. `build_conn()` returns a new connection and functions in this module can be used to manipulate the connection before dispatching to the endpoint. For example, one could set the accepts header for json requests as follows: ``` build_conn() |> put_req_header("accept", "application/json") |> get("/") ``` You can also create your own helpers, such as `json_conn()` that uses [`build_conn/0`](Phoenix.ConnTest.html#build_conn/0) and `put_req_header/3`, so you avoid repeating the connection setup throughout your tests. ## [](Phoenix.ConnTest.html#module-controller-testing)Controller testing The functions in this module can also be used for controller testing. While endpoint testing is preferred over controller testing, especially since the controller in Phoenix plays an integration role between your domain and your views, unit testing controllers may be helpful in some situations. For such cases, you need to set the `@endpoint` attribute to your controller and pass an atom representing the action to dispatch: ``` @endpoint MyAppWeb.HomeController test "says welcome on the home page" do conn = get(build_conn(), :index) assert conn.resp_body =~ "Welcome!" end ``` Keep in mind that, once the `@endpoint` variable is set, all tests after setting it will be affected. ## [](Phoenix.ConnTest.html#module-views-testing)Views testing Under other circumstances, you may be testing a view or another layer that requires a connection for processing. For such cases, a connection can be created using the [`build_conn/3`](Phoenix.ConnTest.html#build_conn/3) helper: ``` MyApp.UserView.render("hello.html", conn: build_conn(:get, "/")) ``` While [`build_conn/0`](Phoenix.ConnTest.html#build_conn/0) returns a connection with no request information to it, [`build_conn/3`](Phoenix.ConnTest.html#build_conn/3) returns a connection with the given request information already filled in. ## [](Phoenix.ConnTest.html#module-recycling)Recycling Browsers implement a storage by using cookies. When a cookie is set in the response, the browser stores it and sends it in the next request. To emulate this behaviour, this module provides the idea of recycling. The [`recycle/1`](Phoenix.ConnTest.html#recycle/1) function receives a connection and returns a new connection, similar to the one returned by [`build_conn/0`](Phoenix.ConnTest.html#build_conn/0) with all the response cookies from the previous connection defined as request headers. This is useful when testing multiple routes that require cookies or session to work. Keep in mind Phoenix will automatically recycle the connection between dispatches. This usually works out well most times, but it may discard information if you are modifying the connection before the next dispatch: ``` # No recycling as the connection is fresh conn = get(build_conn(), "/") # The connection is recycled, creating a new one behind the scenes conn = post(conn, "/login") # We can also recycle manually in case we want custom headers conn = conn |> recycle() |> put_req_header("x-special", "nice") # No recycling as we did it explicitly conn = delete(conn, "/logout") ``` Recycling also recycles the "accept" and "authorization" headers, as well as peer data information. # [](Phoenix.ConnTest.html#summary)Summary ## [Functions](Phoenix.ConnTest.html#functions) [assert\_error\_sent(status\_int\_or\_atom, func)](Phoenix.ConnTest.html#assert_error_sent/2) Asserts an error was wrapped and sent with the given status. [build\_conn()](Phoenix.ConnTest.html#build_conn/0) Creates a connection to be used in upcoming requests. [build\_conn(method, path, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#build_conn/3) Creates a connection to be used in upcoming requests with a preset method, path and body. [bypass\_through(conn)](Phoenix.ConnTest.html#bypass_through/1) Calls the Endpoint and Router pipelines. [bypass\_through(conn, router)](Phoenix.ConnTest.html#bypass_through/2) Calls the Endpoint and Router pipelines for the current route. [bypass\_through(conn, router, pipelines)](Phoenix.ConnTest.html#bypass_through/3) Calls the Endpoint and the given Router pipelines. [clear\_flash(conn)](Phoenix.ConnTest.html#clear_flash/1) Clears up the flash storage. [connect(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#connect/3) Dispatches to the current endpoint. [delete(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#delete/3) Dispatches to the current endpoint. [delete\_req\_cookie(conn, key)](Phoenix.ConnTest.html#delete_req_cookie/2) Deletes a request cookie. [dispatch(conn, endpoint, method, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#dispatch/5) Dispatches the connection to the given endpoint. [ensure\_recycled(conn)](Phoenix.ConnTest.html#ensure_recycled/1) Ensures the connection is recycled if it wasn't already. [fetch\_flash(conn)](Phoenix.ConnTest.html#fetch_flash/1) Fetches the flash storage. [get(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#get/3) Dispatches to the current endpoint. [get\_flash(conn)](Phoenix.ConnTest.html#get_flash/1) deprecated Gets the whole flash storage. [get\_flash(conn, key)](Phoenix.ConnTest.html#get_flash/2) deprecated Gets the given key from the flash storage. [head(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#head/3) Dispatches to the current endpoint. [html\_response(conn, status)](Phoenix.ConnTest.html#html_response/2) Asserts the given status code, that we have an html response and returns the response body if one was set or sent. [init\_test\_session(conn, session)](Phoenix.ConnTest.html#init_test_session/2) Inits a session used exclusively for testing. [json\_response(conn, status)](Phoenix.ConnTest.html#json_response/2) Asserts the given status code, that we have a json response and returns the decoded JSON response if one was set or sent. [options(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#options/3) Dispatches to the current endpoint. [patch(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#patch/3) Dispatches to the current endpoint. [path\_params(conn, to)](Phoenix.ConnTest.html#path_params/2) Returns the matched params of the URL for the `%Plug.Conn{}`'s router. [post(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#post/3) Dispatches to the current endpoint. [put(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#put/3) Dispatches to the current endpoint. [put\_flash(conn, key, value)](Phoenix.ConnTest.html#put_flash/3) Puts the given value under key in the flash storage. [put\_req\_cookie(conn, key, value)](Phoenix.ConnTest.html#put_req_cookie/3) Puts a request cookie. [recycle(conn, headers \\\\ ~w(accept accept-language authorization))](Phoenix.ConnTest.html#recycle/2) Recycles the connection. [redirected\_params(conn, status \\\\ 302)](Phoenix.ConnTest.html#redirected_params/2) Returns the matched params from the URL the connection was redirected to. [redirected\_to(conn, status \\\\ 302)](Phoenix.ConnTest.html#redirected_to/2) Returns the location header from the given redirect response. [response(conn, given)](Phoenix.ConnTest.html#response/2) Asserts the given status code and returns the response body if one was set or sent. [response\_content\_type(conn, format)](Phoenix.ConnTest.html#response_content_type/2) Returns the content type as long as it matches the given format. [text\_response(conn, status)](Phoenix.ConnTest.html#text_response/2) Asserts the given status code, that we have a text response and returns the response body if one was set or sent. [trace(conn, path\_or\_action, params\_or\_body \\\\ nil)](Phoenix.ConnTest.html#trace/3) Dispatches to the current endpoint. # [](Phoenix.ConnTest.html#functions)Functions [](Phoenix.ConnTest.html#assert_error_sent/2) # assert\_error\_sent(status\_int\_or\_atom, func) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L677) ``` @spec assert_error_sent(integer() | atom(), function()) :: {integer(), list(), term()} ``` Asserts an error was wrapped and sent with the given status. Useful for testing actions that you expect raise an error and have the response wrapped in an HTTP status, with content usually rendered by your MyAppWeb.ErrorHTML view. The function accepts a status either as an integer HTTP status or atom, such as `500` or `:internal_server_error`. The list of allowed atoms is available in [`Plug.Conn.Status`](../plug/1.15.3/Plug.Conn.Status.html). If an error is raised, a 3-tuple of the wrapped response is returned matching the status, headers, and body of the response: ``` {500, [{"content-type", "text/html"} | _], "Internal Server Error"} ``` ## [](Phoenix.ConnTest.html#assert_error_sent/2-examples)Examples ``` assert_error_sent :internal_server_error, fn -> get(build_conn(), "/broken/route") end response = assert_error_sent 500, fn -> get(build_conn(), "/broken/route") end assert {500, [_h | _t], "Internal Server Error"} = response ``` This can also be used to test a route resulted in an error that was translated to a specific response by the `Plug.Status` protocol, such as [`Ecto.NoResultsError`](../ecto/3.10.1/Ecto.NoResultsError.html): ``` assert_error_sent :not_found, fn -> get(build_conn(), "/something-that-raises-no-results-error") end ``` *Note*: for routes that don't raise an error, but instead return a status, you should test the response directly: ``` conn = get(build_conn(), "/users/not-found") assert response(conn, 404) ``` [](Phoenix.ConnTest.html#build_conn/0) # build\_conn() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L139) ``` @spec build_conn() :: Plug.Conn.t() ``` Creates a connection to be used in upcoming requests. [](Phoenix.ConnTest.html#build_conn/3) # build\_conn(method, path, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L151) ``` @spec build_conn(atom() | binary(), binary(), binary() | list() | map() | nil) :: Plug.Conn.t() ``` Creates a connection to be used in upcoming requests with a preset method, path and body. This is useful when a specific connection is required for testing a plug or a particular function. [](Phoenix.ConnTest.html#bypass_through/1) # bypass\_through(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L552) ``` @spec bypass_through(Plug.Conn.t()) :: Plug.Conn.t() ``` Calls the Endpoint and Router pipelines. Useful for unit testing Plugs where Endpoint and/or router pipeline plugs are required for proper setup. Note the use of `get("/")` following `bypass_through` in the examples below. To execute the plug pipelines, you must issue a request against the router. Most often, you can simply send a GET request against the root path, but you may also specify a different method or path which your pipelines may operate against. ## [](Phoenix.ConnTest.html#bypass_through/1-examples)Examples For example, imagine you are testing an authentication plug in isolation, but you need to invoke the Endpoint plugs and router pipelines to set up session and flash related dependencies. One option is to invoke an existing route that uses the proper pipelines. You can do so by passing the connection and the router name to `bypass_through`: ``` conn = conn |> bypass_through(MyAppWeb.Router) |> get("/some_url") |> MyApp.RequireAuthentication.call([]) assert conn.halted ``` You can also specify which pipelines you want to run: ``` conn = conn |> bypass_through(MyAppWeb.Router, [:browser]) |> get("/") |> MyApp.RequireAuthentication.call([]) assert conn.halted ``` Alternatively, you could only invoke the Endpoint's plugs: ``` conn = conn |> bypass_through() |> get("/") |> MyApp.RequireAuthentication.call([]) assert conn.halted ``` [](Phoenix.ConnTest.html#bypass_through/2) # bypass\_through(conn, router) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L562) ``` @spec bypass_through(Plug.Conn.t(), module()) :: Plug.Conn.t() ``` Calls the Endpoint and Router pipelines for the current route. See [`bypass_through/1`](Phoenix.ConnTest.html#bypass_through/1). [](Phoenix.ConnTest.html#bypass_through/3) # bypass\_through(conn, router, pipelines) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L572) ``` @spec bypass_through(Plug.Conn.t(), module(), atom() | list()) :: Plug.Conn.t() ``` Calls the Endpoint and the given Router pipelines. See [`bypass_through/1`](Phoenix.ConnTest.html#bypass_through/1). [](Phoenix.ConnTest.html#clear_flash/1) # clear\_flash(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L299) ``` @spec clear_flash(Plug.Conn.t()) :: Plug.Conn.t() ``` Clears up the flash storage. [](Phoenix.ConnTest.html#connect/3) # connect(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#delete/3) # delete(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#delete_req_cookie/2) # delete\_req\_cookie(conn, key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L265) ``` @spec delete_req_cookie(Plug.Conn.t(), binary()) :: Plug.Conn.t() ``` Deletes a request cookie. [](Phoenix.ConnTest.html#dispatch/5) # dispatch(conn, endpoint, method, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L212) Dispatches the connection to the given endpoint. When invoked via [`get/3`](Phoenix.ConnTest.html#get/3), [`post/3`](Phoenix.ConnTest.html#post/3) and friends, the endpoint is automatically retrieved from the `@endpoint` module attribute, otherwise it must be given as an argument. The connection will be configured with the given `method`, `path_or_action` and `params_or_body`. If `path_or_action` is a string, it is considered to be the request path and stored as so in the connection. If an atom, it is assumed to be an action and the connection is dispatched to the given action. ## [](Phoenix.ConnTest.html#dispatch/5-parameters-and-body)Parameters and body This function, as well as [`get/3`](Phoenix.ConnTest.html#get/3), [`post/3`](Phoenix.ConnTest.html#post/3) and friends, accepts the request body or parameters as last argument: ``` get(build_conn(), "/", some: "param") get(build_conn(), "/", "some=param&url=encoded") ``` The allowed values are: - `nil` - meaning there is no body - a binary - containing a request body. For such cases, `:headers` must be given as option with a content-type - a map or list - containing the parameters which will automatically set the content-type to multipart. The map or list may contain other lists or maps and all entries will be normalized to string keys - a struct - unlike other maps, a struct will be passed through as-is without normalizing its entries [](Phoenix.ConnTest.html#ensure_recycled/1) # ensure\_recycled(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L496) ``` @spec ensure_recycled(Plug.Conn.t()) :: Plug.Conn.t() ``` Ensures the connection is recycled if it wasn't already. See [`recycle/1`](Phoenix.ConnTest.html#recycle/1) for more information. [](Phoenix.ConnTest.html#fetch_flash/1) # fetch\_flash(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L271) ``` @spec fetch_flash(Plug.Conn.t()) :: Plug.Conn.t() ``` Fetches the flash storage. [](Phoenix.ConnTest.html#get/3) # get(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#get_flash/1) # get\_flash(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L278) This function is deprecated. get\_flash/1 is deprecated. Use conn.assigns.flash instead. ``` @spec get_flash(Plug.Conn.t()) :: map() ``` Gets the whole flash storage. [](Phoenix.ConnTest.html#get_flash/2) # get\_flash(conn, key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L285) This function is deprecated. get\_flash/2 is deprecated. Use Phoenix.Flash.get/2 instead. ``` @spec get_flash(Plug.Conn.t(), term()) :: term() ``` Gets the given key from the flash storage. [](Phoenix.ConnTest.html#head/3) # head(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#html_response/2) # html\_response(conn, status) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L386) ``` @spec html_response(Plug.Conn.t(), status :: integer() | atom()) :: String.t() ``` Asserts the given status code, that we have an html response and returns the response body if one was set or sent. ## [](Phoenix.ConnTest.html#html_response/2-examples)Examples ``` assert html_response(conn, 200) =~ "<html>" ``` [](Phoenix.ConnTest.html#init_test_session/2) # init\_test\_session(conn, session) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L253) ``` @spec init_test_session(Plug.Conn.t(), map() | keyword()) :: Plug.Conn.t() ``` Inits a session used exclusively for testing. [](Phoenix.ConnTest.html#json_response/2) # json\_response(conn, status) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L418) ``` @spec json_response(Plug.Conn.t(), status :: integer() | atom()) :: term() ``` Asserts the given status code, that we have a json response and returns the decoded JSON response if one was set or sent. ## [](Phoenix.ConnTest.html#json_response/2-examples)Examples ``` body = json_response(conn, 200) assert "can't be blank" in body["errors"] ``` [](Phoenix.ConnTest.html#options/3) # options(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#patch/3) # patch(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#path_params/2) # path\_params(conn, to) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L626) ``` @spec path_params(Plug.Conn.t(), String.t()) :: map() ``` Returns the matched params of the URL for the `%Plug.Conn{}`'s router. Useful for extracting path params out of returned URLs, such as those returned by `Phoenix.LiveViewTest`'s redirected results. ## [](Phoenix.ConnTest.html#path_params/2-examples)Examples ``` assert {:error, {:redirect, %{to: "/posts/123" = to}}} = live(conn, "/path") assert %{id: "123"} = path_params(conn, to) ``` [](Phoenix.ConnTest.html#post/3) # post(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#put/3) # put(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [](Phoenix.ConnTest.html#put_flash/3) # put\_flash(conn, key, value) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L293) ``` @spec put_flash(Plug.Conn.t(), term(), term()) :: Plug.Conn.t() ``` Puts the given value under key in the flash storage. [](Phoenix.ConnTest.html#put_req_cookie/3) # put\_req\_cookie(conn, key, value) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L259) ``` @spec put_req_cookie(Plug.Conn.t(), binary(), binary()) :: Plug.Conn.t() ``` Puts a request cookie. [](Phoenix.ConnTest.html#recycle/2) # recycle(conn, headers \\\\ ~w(accept accept-language authorization)) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L476) ``` @spec recycle(Plug.Conn.t(), [String.t()]) :: Plug.Conn.t() ``` Recycles the connection. Recycling receives a connection and returns a new connection, containing cookies and relevant information from the given one. This emulates behaviour performed by browsers where cookies returned in the response are available in following requests. By default, only the headers "accept", "accept-language", and "authorization" are recycled. However, a custom set of headers can be specified by passing a list of strings representing its names as the second argument of the function. Note [`recycle/1`](Phoenix.ConnTest.html#recycle/1) is automatically invoked when dispatching to the endpoint, unless the connection has already been recycled. [](Phoenix.ConnTest.html#redirected_params/2) # redirected\_params(conn, status \\\\ 302) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L590) ``` @spec redirected_params(Plug.Conn.t(), status :: non_neg_integer()) :: map() ``` Returns the matched params from the URL the connection was redirected to. Uses the provided `%Plug.Conn{}`s router matched in the previous request. Raises if the response's location header is not set or if the response does not match the redirect status code (defaults to 302). ## [](Phoenix.ConnTest.html#redirected_params/2-examples)Examples ``` assert redirected_to(conn) =~ "/posts/123" assert %{id: "123"} = redirected_params(conn) assert %{id: "123"} = redirected_params(conn, 303) ``` [](Phoenix.ConnTest.html#redirected_to/2) # redirected\_to(conn, status \\\\ 302) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L438) ``` @spec redirected_to(Plug.Conn.t(), status :: non_neg_integer()) :: String.t() ``` Returns the location header from the given redirect response. Raises if the response does not match the redirect status code (defaults to 302). ## [](Phoenix.ConnTest.html#redirected_to/2-examples)Examples ``` assert redirected_to(conn) =~ "/foo/bar" assert redirected_to(conn, 301) =~ "/foo/bar" assert redirected_to(conn, :moved_permanently) =~ "/foo/bar" ``` [](Phoenix.ConnTest.html#response/2) # response(conn, given) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L357) ``` @spec response(Plug.Conn.t(), status :: integer() | atom()) :: binary() ``` Asserts the given status code and returns the response body if one was set or sent. ## [](Phoenix.ConnTest.html#response/2-examples)Examples ``` conn = get(build_conn(), "/") assert response(conn, 200) =~ "hello world" ``` [](Phoenix.ConnTest.html#response_content_type/2) # response\_content\_type(conn, format) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L311) ``` @spec response_content_type(Plug.Conn.t(), atom()) :: String.t() ``` Returns the content type as long as it matches the given format. ## [](Phoenix.ConnTest.html#response_content_type/2-examples)Examples ``` # Assert we have an html response with utf-8 charset assert response_content_type(conn, :html) =~ "charset=utf-8" ``` [](Phoenix.ConnTest.html#text_response/2) # text\_response(conn, status) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L401) ``` @spec text_response(Plug.Conn.t(), status :: integer() | atom()) :: String.t() ``` Asserts the given status code, that we have a text response and returns the response body if one was set or sent. ## [](Phoenix.ConnTest.html#text_response/2-examples)Examples ``` assert text_response(conn, 200) =~ "hello" ``` [](Phoenix.ConnTest.html#trace/3) # trace(conn, path\_or\_action, params\_or\_body \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/test/conn_test.ex#L165)(macro) Dispatches to the current endpoint. See [`dispatch/5`](Phoenix.ConnTest.html#dispatch/5) for more information. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Controller.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1 "View Source") Phoenix.Controller (Phoenix v1.7.18) Controllers are used to group common functionality in the same (pluggable) module. For example, the route: ``` get "/users/:id", MyAppWeb.UserController, :show ``` will invoke the `show/2` action in the `MyAppWeb.UserController`: ``` defmodule MyAppWeb.UserController do use MyAppWeb, :controller def show(conn, %{"id" => id}) do user = Repo.get(User, id) render(conn, :show, user: user) end end ``` An action is a regular function that receives the connection and the request parameters as arguments. The connection is a [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) struct, as specified by the Plug library. Then we invoke [`render/3`](Phoenix.Controller.html#render/3), passing the connection, the template to render (typically named after the action), and the `user: user` as assigns. We will explore all of those concepts next. ## [](Phoenix.Controller.html#module-connection)Connection A controller by default provides many convenience functions for manipulating the connection, rendering templates, and more. Those functions are imported from two modules: - [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) - a collection of low-level functions to work with the connection - [`Phoenix.Controller`](Phoenix.Controller.html) - functions provided by Phoenix to support rendering, and other Phoenix specific behaviour If you want to have functions that manipulate the connection without fully implementing the controller, you can import both modules directly instead of `use Phoenix.Controller`. ## [](Phoenix.Controller.html#module-rendering-and-layouts)Rendering and layouts One of the main features provided by controllers is the ability to perform content negotiation and render templates based on information sent by the client. There are two ways to render content in a controller. One option is to invoke format-specific functions, such as [`html/2`](Phoenix.Controller.html#html/2) and [`json/2`](Phoenix.Controller.html#json/2). However, most commonly controllers invoke custom modules called views. Views are modules capable of rendering a custom format. This is done by specifying the option `:formats` when defining the controller: ``` use Phoenix.Controller, formats: [:html, :json] ``` Now, when invoking [`render/3`](Phoenix.Controller.html#render/3), a controller named `MyAppWeb.UserController` will invoke `MyAppWeb.UserHTML` and `MyAppWeb.UserJSON` respectively when rendering each format: ``` def show(conn, %{"id" => id}) do user = Repo.get(User, id) # Will invoke UserHTML.show(%{user: user}) for html requests # Will invoke UserJSON.show(%{user: user}) for json requests render(conn, :show, user: user) end ``` Some formats are also handy to have layouts, which render content shared across all pages. We can also specify layouts on `use`: ``` use Phoenix.Controller, formats: [:html, :json], layouts: [html: MyAppWeb.Layouts] ``` You can also specify formats and layouts to render by calling [`put_view/2`](Phoenix.Controller.html#put_view/2) and [`put_layout/2`](Phoenix.Controller.html#put_layout/2) directly with a connection. The line above can also be written directly in your actions as: ``` conn |> put_view(html: MyAppWeb.UserHTML, json: MyAppWeb.UserJSON) |> put_layout(html: MyAppWeb.Layouts) ``` ### [](Phoenix.Controller.html#module-backwards-compatibility)Backwards compatibility In previous Phoenix versions, a controller you always render `MyApp.UserView`. This behaviour can be explicitly retained by passing a suffix to the formats options: ``` use Phoenix.Controller, formats: [html: "View", json: "View"], layouts: [html: MyAppWeb.Layouts] ``` ### [](Phoenix.Controller.html#module-options)Options When used, the controller supports the following options to customize template rendering: - `:formats` - the formats this controller will render by default. For example, specifying `formats: [:html, :json]` for a controller named `MyAppWeb.UserController` will invoke `MyAppWeb.UserHTML` and `MyAppWeb.UserJSON` when respectively rendering each format. If `:formats` is not set, the default view is set to `MyAppWeb.UserView` - `:layouts` - which layouts to render for each format, for example: `[html: DemoWeb.Layouts]` Deprecated options: - `:namespace` - sets the namespace for the layout. Use `:layouts` instead - `:put_default_views` - controls whether the default view and layout should be set or not. Set `formats: []` and `layouts: []` instead ## [](Phoenix.Controller.html#module-plug-pipeline)Plug pipeline As with routers, controllers also have their own plug pipeline. However, different from routers, controllers have a single pipeline: ``` defmodule MyAppWeb.UserController do use MyAppWeb, :controller plug :authenticate, usernames: ["jose", "eric", "sonny"] def show(conn, params) do # authenticated users only end defp authenticate(conn, options) do if get_session(conn, :username) in options[:usernames] do conn else conn |> redirect(to: "/") |> halt() end end end ``` The `:authenticate` plug will be invoked before the action. If the plug calls [`Plug.Conn.halt/1`](../plug/1.15.3/Plug.Conn.html#halt/1) (which is by default imported into controllers), it will halt the pipeline and won't invoke the action. ### [](Phoenix.Controller.html#module-guards)Guards `plug/2` in controllers supports guards, allowing a developer to configure a plug to only run in some particular action. ``` plug :do_something when action in [:show, :edit] ``` Due to operator precedence in Elixir, if the second argument is a keyword list, we need to wrap the keyword in `[...]` when using `when`: ``` plug :authenticate, [usernames: ["jose", "eric", "sonny"]] when action in [:show, :edit] plug :authenticate, [usernames: ["admin"]] when not action in [:index] ``` The first plug will run only when action is show or edit. The second plug will always run, except for the index action. Those guards work like regular Elixir guards and the only variables accessible in the guard are `conn`, the `action` as an atom and the `controller` as an alias. ## [](Phoenix.Controller.html#module-controllers-are-plugs)Controllers are plugs Like routers, controllers are plugs, but they are wired to dispatch to a particular function which is called an action. For example, the route: ``` get "/users/:id", UserController, :show ``` will invoke `UserController` as a plug: ``` UserController.call(conn, :show) ``` which will trigger the plug pipeline and which will eventually invoke the inner action plug that dispatches to the `show/2` function in `UserController`. As controllers are plugs, they implement both [`init/1`](../plug/1.15.3/Plug.html#c:init/1) and [`call/2`](../plug/1.15.3/Plug.html#c:call/2), and it also provides a function named `action/2` which is responsible for dispatching the appropriate action after the plug stack (and is also overridable). ### [](Phoenix.Controller.html#module-overriding-action-2-for-custom-arguments)Overriding `action/2` for custom arguments Phoenix injects an `action/2` plug in your controller which calls the function matched from the router. By default, it passes the conn and params. In some cases, overriding the `action/2` plug in your controller is a useful way to inject arguments into your actions that you would otherwise need to repeatedly fetch off the connection. For example, imagine if you stored a `conn.assigns.current_user` in the connection and wanted quick access to the user for every action in your controller: ``` def action(conn, _) do args = [conn, conn.params, conn.assigns.current_user] apply(__MODULE__, action_name(conn), args) end def index(conn, _params, user) do videos = Repo.all(user_videos(user)) # ... end def delete(conn, %{"id" => id}, user) do video = Repo.get!(user_videos(user), id) # ... end ``` # [](Phoenix.Controller.html#summary)Summary ## [Types](Phoenix.Controller.html#types) [layout()](Phoenix.Controller.html#t:layout/0) [view()](Phoenix.Controller.html#t:view/0) ## [Functions](Phoenix.Controller.html#functions) [accepts(conn, accepted)](Phoenix.Controller.html#accepts/2) Performs content negotiation based on the available formats. [action\_fallback(plug)](Phoenix.Controller.html#action_fallback/1) Registers the plug to call as a fallback to the controller action. [action\_name(conn)](Phoenix.Controller.html#action_name/1) Returns the action name as an atom, raises if unavailable. [allow\_jsonp(conn, opts \\\\ \[\])](Phoenix.Controller.html#allow_jsonp/2) A plug that may convert a JSON response into a JSONP one. [clear\_flash(conn)](Phoenix.Controller.html#clear_flash/1) Clears all flash messages. [controller\_module(conn)](Phoenix.Controller.html#controller_module/1) Returns the controller module as an atom, raises if unavailable. [current\_path(conn)](Phoenix.Controller.html#current_path/1) Returns the current request path with its default query parameters [current\_path(conn, params)](Phoenix.Controller.html#current_path/2) Returns the current path with the given query parameters. [current\_url(conn)](Phoenix.Controller.html#current_url/1) Returns the current request url with its default query parameters [current\_url(conn, params)](Phoenix.Controller.html#current_url/2) Returns the current request URL with query params. [delete\_csrf\_token()](Phoenix.Controller.html#delete_csrf_token/0) Deletes the CSRF token from the process dictionary. [endpoint\_module(conn)](Phoenix.Controller.html#endpoint_module/1) Returns the endpoint module as an atom, raises if unavailable. [fetch\_flash(conn, opts \\\\ \[\])](Phoenix.Controller.html#fetch_flash/2) Fetches the flash storage. [get\_csrf\_token()](Phoenix.Controller.html#get_csrf_token/0) Gets or generates a CSRF token. [get\_flash(conn)](Phoenix.Controller.html#get_flash/1) deprecated Returns a map of previously set flash messages or an empty map. [get\_flash(conn, key)](Phoenix.Controller.html#get_flash/2) deprecated Returns a message from flash by `key` (or `nil` if no message is available for `key`). [get\_format(conn)](Phoenix.Controller.html#get_format/1) Returns the request format, such as "json", "html". [html(conn, data)](Phoenix.Controller.html#html/2) Sends html response. [json(conn, data)](Phoenix.Controller.html#json/2) Sends JSON response. [layout(conn, format \\\\ nil)](Phoenix.Controller.html#layout/2) Retrieves the current layout for the given format. [layout\_formats(conn)](Phoenix.Controller.html#layout_formats/1) deprecated Retrieves current layout formats. [merge\_flash(conn, enumerable)](Phoenix.Controller.html#merge_flash/2) Merges a map into the flash. [protect\_from\_forgery(conn, opts \\\\ \[\])](Phoenix.Controller.html#protect_from_forgery/2) Enables CSRF protection. [put\_flash(conn, key, message)](Phoenix.Controller.html#put_flash/3) Persists a value in flash. [put\_format(conn, format)](Phoenix.Controller.html#put_format/2) Puts the format in the connection. [put\_layout(conn, layout)](Phoenix.Controller.html#put_layout/2) Stores the layout for rendering. [put\_layout\_formats(conn, formats)](Phoenix.Controller.html#put_layout_formats/2) deprecated Sets which formats have a layout when rendering. [put\_new\_layout(conn, layout)](Phoenix.Controller.html#put_new_layout/2) Stores the layout for rendering if one was not stored yet. [put\_new\_view(conn, formats)](Phoenix.Controller.html#put_new_view/2) Stores the view for rendering if one was not stored yet. [put\_root\_layout(conn, layout)](Phoenix.Controller.html#put_root_layout/2) Stores the root layout for rendering. [put\_router\_url(conn, uri)](Phoenix.Controller.html#put_router_url/2) Puts the url string or `%URI{}` to be used for route generation. [put\_secure\_browser\_headers(conn, headers \\\\ %{})](Phoenix.Controller.html#put_secure_browser_headers/2) Put headers that improve browser security. [put\_static\_url(conn, uri)](Phoenix.Controller.html#put_static_url/2) Puts the URL or `%URI{}` to be used for the static url generation. [put\_view(conn, formats)](Phoenix.Controller.html#put_view/2) Stores the view for rendering. [redirect(conn, opts)](Phoenix.Controller.html#redirect/2) Sends redirect response to the given url. [render(conn, template\_or\_assigns \\\\ \[\])](Phoenix.Controller.html#render/2) Render the given template or the default template specified by the current action with the given assigns. [render(conn, template, assigns)](Phoenix.Controller.html#render/3) Renders the given `template` and `assigns` based on the `conn` information. [root\_layout(conn, format \\\\ nil)](Phoenix.Controller.html#root_layout/2) Retrieves the current root layout for the given format. [router\_module(conn)](Phoenix.Controller.html#router_module/1) Returns the router module as an atom, raises if unavailable. [scrub\_params(conn, required\_key)](Phoenix.Controller.html#scrub_params/2) Scrubs the parameters from the request. [send\_download(conn, kind, opts \\\\ \[\])](Phoenix.Controller.html#send_download/3) Sends the given file or binary as a download. [status\_message\_from\_template(template)](Phoenix.Controller.html#status_message_from_template/1) Generates a status message from the template name. [text(conn, data)](Phoenix.Controller.html#text/2) Sends text response. [view\_module(conn, format \\\\ nil)](Phoenix.Controller.html#view_module/2) Retrieves the current view for the given format. [view\_template(conn)](Phoenix.Controller.html#view_template/1) Returns the template name rendered in the view as a string (or nil if no template was rendered). # [](Phoenix.Controller.html#types)Types [](Phoenix.Controller.html#t:layout/0) # layout() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L16) ``` @type layout() :: {module(), layout_name :: atom()} | atom() | false ``` [](Phoenix.Controller.html#t:view/0) # view() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L15) ``` @type view() :: atom() ``` # [](Phoenix.Controller.html#functions)Functions [](Phoenix.Controller.html#accepts/2) # accepts(conn, accepted) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1507) ``` @spec accepts(Plug.Conn.t(), [binary()]) :: Plug.Conn.t() ``` Performs content negotiation based on the available formats. It receives a connection, a list of formats that the server is capable of rendering and then proceeds to perform content negotiation based on the request information. If the client accepts any of the given formats, the request proceeds. If the request contains a "\_format" parameter, it is considered to be the format desired by the client. If no "\_format" parameter is available, this function will parse the "accept" header and find a matching format accordingly. This function is useful when you may want to serve different content-types (such as JSON and HTML) from the same routes. However, if you always have distinct routes, you can also disable content negotiation and simply hardcode your format of choice in your route pipelines: ``` plug :put_format, "html" ``` It is important to notice that browsers have historically sent bad accept headers. For this reason, this function will default to "html" format whenever: - the accepted list of arguments contains the "html" format - the accept header specified more than one media type preceded or followed by the wildcard media type "`*/*`" This function raises [`Phoenix.NotAcceptableError`](Phoenix.NotAcceptableError.html), which is rendered with status 406, whenever the server cannot serve a response in any of the formats expected by the client. ## [](Phoenix.Controller.html#accepts/2-examples)Examples [`accepts/2`](Phoenix.Controller.html#accepts/2) can be invoked as a function: ``` iex> accepts(conn, ["html", "json"]) ``` or used as a plug: ``` plug :accepts, ["html", "json"] plug :accepts, ~w(html json) ``` ## [](Phoenix.Controller.html#accepts/2-custom-media-types)Custom media types It is possible to add custom media types to your Phoenix application. The first step is to teach Plug about those new media types in your `config/config.exs` file: ``` config :mime, :types, %{ "application/vnd.api+json" => ["json-api"] } ``` The key is the media type, the value is a list of formats the media type can be identified with. For example, by using "json-api", you will be able to use templates with extension "index.json-api" or to force a particular format in a given URL by sending "?\_format=json-api". After this change, you must recompile plug: ``` $ mix deps.clean mime --build $ mix deps.get ``` And now you can use it in accepts too: ``` plug :accepts, ["html", "json-api"] ``` [](Phoenix.Controller.html#action_fallback/1) # action\_fallback(plug) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L314)(macro) Registers the plug to call as a fallback to the controller action. A fallback plug is useful to translate common domain data structures into a valid `%Plug.Conn{}` response. If the controller action fails to return a `%Plug.Conn{}`, the provided plug will be called and receive the controller's `%Plug.Conn{}` as it was before the action was invoked along with the value returned from the controller action. ## [](Phoenix.Controller.html#action_fallback/1-examples)Examples ``` defmodule MyController do use Phoenix.Controller action_fallback MyFallbackController def show(conn, %{"id" => id}, current_user) do with {:ok, post} <- Blog.fetch_post(id), :ok <- Authorizer.authorize(current_user, :view, post) do render(conn, "show.json", post: post) end end end ``` In the above example, `with` is used to match only a successful post fetch, followed by valid authorization for the current user. In the event either of those fail to match, `with` will not invoke the render block and instead return the unmatched value. In this case, imagine `Blog.fetch_post/2` returned `{:error, :not_found}` or `Authorizer.authorize/3` returned `{:error, :unauthorized}`. For cases where these data structures serve as return values across multiple boundaries in our domain, a single fallback module can be used to translate the value into a valid response. For example, you could write the following fallback controller to handle the above values: ``` defmodule MyFallbackController do use Phoenix.Controller def call(conn, {:error, :not_found}) do conn |> put_status(:not_found) |> put_view(MyErrorView) |> render(:"404") end def call(conn, {:error, :unauthorized}) do conn |> put_status(:forbidden) |> put_view(MyErrorView) |> render(:"403") end end ``` [](Phoenix.Controller.html#action_name/1) # action\_name(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L322) ``` @spec action_name(Plug.Conn.t()) :: atom() ``` Returns the action name as an atom, raises if unavailable. [](Phoenix.Controller.html#allow_jsonp/2) # allow\_jsonp(conn, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L392) ``` @spec allow_jsonp(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t() ``` A plug that may convert a JSON response into a JSONP one. In case a JSON response is returned, it will be converted to a JSONP as long as the callback field is present in the query string. The callback field itself defaults to "callback", but may be configured with the callback option. In case there is no callback or the response is not encoded in JSON format, it is a no-op. Only alphanumeric characters and underscore are allowed in the callback name. Otherwise an exception is raised. ## [](Phoenix.Controller.html#allow_jsonp/2-examples)Examples ``` # Will convert JSON to JSONP if callback=someFunction is given plug :allow_jsonp # Will convert JSON to JSONP if cb=someFunction is given plug :allow_jsonp, callback: "cb" ``` [](Phoenix.Controller.html#clear_flash/1) # clear\_flash(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1749) Clears all flash messages. [](Phoenix.Controller.html#controller_module/1) # controller\_module(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L328) ``` @spec controller_module(Plug.Conn.t()) :: atom() ``` Returns the controller module as an atom, raises if unavailable. [](Phoenix.Controller.html#current_path/1) # current\_path(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1772) Returns the current request path with its default query parameters: ``` iex> current_path(conn) "/users/123?existing=param" ``` See [`current_path/2`](Phoenix.Controller.html#current_path/2) to override the default parameters. The path is normalized based on the `conn.script_name` and `conn.path_info`. For example, "/foo//bar/" will become "/foo/bar". If you want the original path, use `conn.request_path` instead. [](Phoenix.Controller.html#current_path/2) # current\_path(conn, params) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1804) Returns the current path with the given query parameters. You may also retrieve only the request path by passing an empty map of params. ## [](Phoenix.Controller.html#current_path/2-examples)Examples ``` iex> current_path(conn) "/users/123?existing=param" iex> current_path(conn, %{new: "param"}) "/users/123?new=param" iex> current_path(conn, %{filter: %{status: ["draft", "published"]}}) "/users/123?filter[status][]=draft&filter[status][]=published" iex> current_path(conn, %{}) "/users/123" ``` The path is normalized based on the `conn.script_name` and `conn.path_info`. For example, "/foo//bar/" will become "/foo/bar". If you want the original path, use `conn.request_path` instead. [](Phoenix.Controller.html#current_url/1) # current\_url(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1824) Returns the current request url with its default query parameters: ``` iex> current_url(conn) "https://www.example.com/users/123?existing=param" ``` See [`current_url/2`](Phoenix.Controller.html#current_url/2) to override the default parameters. [](Phoenix.Controller.html#current_url/2) # current\_url(conn, params) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1870) Returns the current request URL with query params. The path will be retrieved from the currently requested path via [`current_path/1`](Phoenix.Controller.html#current_path/1). The scheme, host and others will be received from the URL configuration in your Phoenix endpoint. The reason we don't use the host and scheme information in the request is because most applications are behind proxies and the host and scheme may not actually reflect the host and scheme accessed by the client. If you want to access the url precisely as requested by the client, see [`Plug.Conn.request_url/1`](../plug/1.15.3/Plug.Conn.html#request_url/1). ## [](Phoenix.Controller.html#current_url/2-examples)Examples ``` iex> current_url(conn) "https://www.example.com/users/123?existing=param" iex> current_url(conn, %{new: "param"}) "https://www.example.com/users/123?new=param" iex> current_url(conn, %{}) "https://www.example.com/users/123" ``` ## [](Phoenix.Controller.html#current_url/2-custom-url-generation)Custom URL Generation In some cases, you'll need to generate a request's URL, but using a different scheme, different host, etc. This can be accomplished in two ways. If you want to do so in a case-by-case basis, you can define a custom function that gets the endpoint URI configuration and changes it accordingly. For example, to get the current URL always in HTTPS format: ``` def current_secure_url(conn, params \\ %{}) do current_uri = MyAppWeb.Endpoint.struct_url() current_path = Phoenix.Controller.current_path(conn, params) Phoenix.VerifiedRoutes.unverified_url(%URI{current_uri | scheme: "https"}, current_path) end ``` However, if you want all generated URLs to always have a certain schema, host, etc, you may use [`put_router_url/2`](Phoenix.Controller.html#put_router_url/2). [](Phoenix.Controller.html#delete_csrf_token/0) # delete\_csrf\_token() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1433) Deletes the CSRF token from the process dictionary. *Note*: The token is deleted only after a response has been sent. [](Phoenix.Controller.html#endpoint_module/1) # endpoint\_module(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L340) ``` @spec endpoint_module(Plug.Conn.t()) :: atom() ``` Returns the endpoint module as an atom, raises if unavailable. [](Phoenix.Controller.html#fetch_flash/2) # fetch\_flash(conn, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1629) Fetches the flash storage. [](Phoenix.Controller.html#get_csrf_token/0) # get\_csrf\_token() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1426) Gets or generates a CSRF token. If a token exists, it is returned, otherwise it is generated and stored in the process dictionary. [](Phoenix.Controller.html#get_flash/1) # get\_flash(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1705) This function is deprecated. get\_flash/1 is deprecated. Use the @flash assign provided by the :fetch\_flash plug. Returns a map of previously set flash messages or an empty map. ## [](Phoenix.Controller.html#get_flash/1-examples)Examples ``` iex> get_flash(conn) %{} iex> conn = put_flash(conn, :info, "Welcome Back!") iex> get_flash(conn) %{"info" => "Welcome Back!"} ``` [](Phoenix.Controller.html#get_flash/2) # get\_flash(conn, key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1721) This function is deprecated. get\_flash/2 is deprecated. Use Phoenix.Flash.get(@flash, key) instead. Returns a message from flash by `key` (or `nil` if no message is available for `key`). ## [](Phoenix.Controller.html#get_flash/2-examples)Examples ``` iex> conn = put_flash(conn, :info, "Welcome Back!") iex> get_flash(conn, :info) "Welcome Back!" ``` [](Phoenix.Controller.html#get_format/1) # get\_format(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1166) Returns the request format, such as "json", "html". This format is used when rendering a template as an atom. For example, `render(conn, :foo)` will render `"foo.FORMAT"` where the format is the one set here. The default format is typically set from the negotiation done in [`accepts/2`](Phoenix.Controller.html#accepts/2). [](Phoenix.Controller.html#html/2) # html(conn, data) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L468) ``` @spec html(Plug.Conn.t(), iodata()) :: Plug.Conn.t() ``` Sends html response. ## [](Phoenix.Controller.html#html/2-examples)Examples ``` iex> html(conn, "<html><head>...") ``` [](Phoenix.Controller.html#json/2) # json(conn, data) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L363) ``` @spec json(Plug.Conn.t(), term()) :: Plug.Conn.t() ``` Sends JSON response. It uses the configured `:json_library` under the `:phoenix` application for `:json` to pick up the encoder module. ## [](Phoenix.Controller.html#json/2-examples)Examples ``` iex> json(conn, %{id: 123}) ``` [](Phoenix.Controller.html#layout/2) # layout(conn, format \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L804) ``` @spec layout(Plug.Conn.t(), binary() | nil) :: {atom(), String.t() | atom()} | false ``` Retrieves the current layout for the given format. If no format is given, takes the current one from the connection. [](Phoenix.Controller.html#layout_formats/1) # layout\_formats(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L794) This function is deprecated. layout\_formats/1 is deprecated, pass a keyword list to put\_layout/put\_root\_layout instead. ``` @spec layout_formats(Plug.Conn.t()) :: [String.t()] ``` Retrieves current layout formats. [](Phoenix.Controller.html#merge_flash/2) # merge\_flash(conn, enumerable) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1666) Merges a map into the flash. Returns the updated connection. ## [](Phoenix.Controller.html#merge_flash/2-examples)Examples ``` iex> conn = merge_flash(conn, info: "Welcome Back!") iex> Phoenix.Flash.get(conn.assigns.flash, :info) "Welcome Back!" ``` [](Phoenix.Controller.html#protect_from_forgery/2) # protect\_from\_forgery(conn, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1368) Enables CSRF protection. Currently used as a wrapper function for [`Plug.CSRFProtection`](../plug/1.15.3/Plug.CSRFProtection.html) and mainly serves as a function plug in `YourApp.Router`. Check [`get_csrf_token/0`](Phoenix.Controller.html#get_csrf_token/0) and [`delete_csrf_token/0`](Phoenix.Controller.html#delete_csrf_token/0) for retrieving and deleting CSRF tokens. [](Phoenix.Controller.html#put_flash/3) # put\_flash(conn, key, message) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1683) Persists a value in flash. Returns the updated connection. ## [](Phoenix.Controller.html#put_flash/3-examples)Examples ``` iex> conn = put_flash(conn, :info, "Welcome Back!") iex> Phoenix.Flash.get(conn.assigns.flash, :info) "Welcome Back!" ``` [](Phoenix.Controller.html#put_format/2) # put\_format(conn, format) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1156) Puts the format in the connection. This format is used when rendering a template as an atom. For example, `render(conn, :foo)` will render `"foo.FORMAT"` where the format is the one set here. The default format is typically set from the negotiation done in [`accepts/2`](Phoenix.Controller.html#accepts/2). See [`get_format/1`](Phoenix.Controller.html#get_format/1) for retrieval. [](Phoenix.Controller.html#put_layout/2) # put\_layout(conn, layout) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L641) ``` @spec put_layout(Plug.Conn.t(), [{format :: atom(), layout()}] | false) :: Plug.Conn.t() ``` Stores the layout for rendering. The layout must be given as keyword list where the key is the request format the layout will be applied to (such as `:html`) and the value is one of: - `{module, layout}` with the `module` the layout is defined and the name of the `layout` as an atom - `layout` when the name of the layout. This requires a layout for the given format in the shape of `{module, layout}` to be previously given - `false` which disables the layout If `false` is given without a format, all layouts are disabled. ## [](Phoenix.Controller.html#put_layout/2-examples)Examples ``` iex> layout(conn) false iex> conn = put_layout(conn, html: {AppView, :application}) iex> layout(conn) {AppView, :application} iex> conn = put_layout(conn, html: :print) iex> layout(conn) {AppView, :print} ``` Raises [`Plug.Conn.AlreadySentError`](../plug/1.15.3/Plug.Conn.AlreadySentError.html) if `conn` is already sent. [](Phoenix.Controller.html#put_layout_formats/2) # put\_layout\_formats(conn, formats) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L782) This function is deprecated. put\_layout\_formats/2 is deprecated, pass a keyword list to put\_layout/put\_root\_layout instead. ``` @spec put_layout_formats(Plug.Conn.t(), [String.t()]) :: Plug.Conn.t() ``` Sets which formats have a layout when rendering. ## [](Phoenix.Controller.html#put_layout_formats/2-examples)Examples ``` iex> layout_formats(conn) ["html"] iex> put_layout_formats(conn, ["html", "mobile"]) iex> layout_formats(conn) ["html", "mobile"] ``` Raises [`Plug.Conn.AlreadySentError`](../plug/1.15.3/Plug.Conn.AlreadySentError.html) if `conn` is already sent. [](Phoenix.Controller.html#put_new_layout/2) # put\_new\_layout(conn, layout) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L719) ``` @spec put_new_layout(Plug.Conn.t(), [{format :: atom(), layout()}] | layout()) :: Plug.Conn.t() ``` Stores the layout for rendering if one was not stored yet. See [`put_layout/2`](Phoenix.Controller.html#put_layout/2) for more information. Raises [`Plug.Conn.AlreadySentError`](../plug/1.15.3/Plug.Conn.AlreadySentError.html) if `conn` is already sent. [](Phoenix.Controller.html#put_new_view/2) # put\_new\_view(conn, formats) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L577) ``` @spec put_new_view(Plug.Conn.t(), [{format :: atom(), view()}] | view()) :: Plug.Conn.t() ``` Stores the view for rendering if one was not stored yet. Raises [`Plug.Conn.AlreadySentError`](../plug/1.15.3/Plug.Conn.AlreadySentError.html) if `conn` is already sent. [](Phoenix.Controller.html#put_root_layout/2) # put\_root\_layout(conn, layout) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L758) ``` @spec put_root_layout(Plug.Conn.t(), [{format :: atom(), layout()}] | false) :: Plug.Conn.t() ``` Stores the root layout for rendering. The layout must be given as keyword list where the key is the request format the layout will be applied to (such as `:html`) and the value is one of: - `{module, layout}` with the `module` the layout is defined and the name of the `layout` as an atom - `layout` when the name of the layout. This requires a layout for the given format in the shape of `{module, layout}` to be previously given - `false` which disables the layout ## [](Phoenix.Controller.html#put_root_layout/2-examples)Examples ``` iex> root_layout(conn) false iex> conn = put_root_layout(conn, html: {AppView, :root}) iex> root_layout(conn) {AppView, :root} iex> conn = put_root_layout(conn, html: :bare) iex> root_layout(conn) {AppView, :bare} ``` Raises [`Plug.Conn.AlreadySentError`](../plug/1.15.3/Plug.Conn.AlreadySentError.html) if `conn` is already sent. [](Phoenix.Controller.html#put_router_url/2) # put\_router\_url(conn, uri) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1123) Puts the url string or `%URI{}` to be used for route generation. This function overrides the default URL generation pulled from the `%Plug.Conn{}`'s endpoint configuration. ## [](Phoenix.Controller.html#put_router_url/2-examples)Examples Imagine your application is configured to run on "example.com" but after the user signs in, you want all links to use "some\_user.example.com". You can do so by setting the proper router url configuration: ``` def put_router_url_by_user(conn) do put_router_url(conn, get_user_from_conn(conn).account_name <> ".example.com") end ``` Now when you call `Routes.some_route_url(conn, ...)`, it will use the router url set above. Keep in mind that, if you want to generate routes to the *current* domain, it is preferred to use `Routes.some_route_path` helpers, as those are always relative. [](Phoenix.Controller.html#put_secure_browser_headers/2) # put\_secure\_browser\_headers(conn, headers \\\\ %{}) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1395) Put headers that improve browser security. It sets the following headers: - `referrer-policy` - only send origin on cross origin requests - `x-frame-options` - set to SAMEORIGIN to avoid clickjacking through iframes unless in the same origin - `x-content-type-options` - set to nosniff. This requires script and style tags to be sent with proper content type - `x-download-options` - set to noopen to instruct the browser not to open a download directly in the browser, to avoid HTML files rendering inline and accessing the security context of the application (like critical domain cookies) - `x-permitted-cross-domain-policies` - set to none to restrict Adobe Flash Player’s access to data A custom headers map may also be given to be merged with defaults. It is recommended for custom header keys to be in lowercase, to avoid sending duplicate keys in a request. Additionally, responses with mixed-case headers served over HTTP/2 are not considered valid by common clients, resulting in dropped responses. [](Phoenix.Controller.html#put_static_url/2) # put\_static\_url(conn, uri) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1138) Puts the URL or `%URI{}` to be used for the static url generation. Using this function on a `%Plug.Conn{}` struct tells `static_url/2` to use the given information for URL generation instead of the `%Plug.Conn{}`'s endpoint configuration (much like [`put_router_url/2`](Phoenix.Controller.html#put_router_url/2) but for static URLs). [](Phoenix.Controller.html#put_view/2) # put\_view(conn, formats) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L540) ``` @spec put_view(Plug.Conn.t(), [{format :: atom(), view()}] | view()) :: Plug.Conn.t() ``` Stores the view for rendering. Raises [`Plug.Conn.AlreadySentError`](../plug/1.15.3/Plug.Conn.AlreadySentError.html) if `conn` is already sent. ## [](Phoenix.Controller.html#put_view/2-examples)Examples ``` # Use single view module iex> put_view(conn, AppView) # Use multiple view module for content negotiation iex> put_view(conn, html: AppHTML, json: AppJSON) ``` [](Phoenix.Controller.html#redirect/2) # redirect(conn, opts) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L489) Sends redirect response to the given url. For security, `:to` only accepts paths. Use the `:external` option to redirect to any URL. The response will be sent with the status code defined within the connection, via [`Plug.Conn.put_status/2`](../plug/1.15.3/Plug.Conn.html#put_status/2). If no status code is set, a 302 response is sent. ## [](Phoenix.Controller.html#redirect/2-examples)Examples ``` iex> redirect(conn, to: "/login") iex> redirect(conn, external: "https://elixir-lang.org") ``` [](Phoenix.Controller.html#render/2) # render(conn, template\_or\_assigns \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L835) ``` @spec render(Plug.Conn.t(), Keyword.t() | map() | binary() | atom()) :: Plug.Conn.t() ``` Render the given template or the default template specified by the current action with the given assigns. See [`render/3`](Phoenix.Controller.html#render/3) for more information. [](Phoenix.Controller.html#render/3) # render(conn, template, assigns) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L937) ``` @spec render(Plug.Conn.t(), binary() | atom(), Keyword.t() | map()) :: Plug.Conn.t() ``` Renders the given `template` and `assigns` based on the `conn` information. Once the template is rendered, the template format is set as the response content type (for example, an HTML template will set "text/html" as response content type) and the data is sent to the client with default status of 200. ## [](Phoenix.Controller.html#render/3-arguments)Arguments - `conn` - the [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) struct - `template` - which may be an atom or a string. If an atom, like `:index`, it will render a template with the same format as the one returned by [`get_format/1`](Phoenix.Controller.html#get_format/1). For example, for an HTML request, it will render the "index.html" template. If the template is a string, it must contain the extension too, like "index.json" - `assigns` - a dictionary with the assigns to be used in the view. Those assigns are merged and have higher precedence than the connection assigns (`conn.assigns`) ## [](Phoenix.Controller.html#render/3-examples)Examples ``` defmodule MyAppWeb.UserController do use Phoenix.Controller def show(conn, _params) do render(conn, "show.html", message: "Hello") end end ``` The example above renders a template "show.html" from the `MyAppWeb.UserView` and sets the response content type to "text/html". In many cases, you may want the template format to be set dynamically based on the request. To do so, you can pass the template name as an atom (without the extension): ``` def show(conn, _params) do render(conn, :show, message: "Hello") end ``` In order for the example above to work, we need to do content negotiation with the accepts plug before rendering. You can do so by adding the following to your pipeline (in the router): ``` plug :accepts, ["html"] ``` ## [](Phoenix.Controller.html#render/3-views)Views By default, Controllers render templates in a view with a similar name to the controller. For example, `MyAppWeb.UserController` will render templates inside the `MyAppWeb.UserView`. This information can be changed any time by using the [`put_view/2`](Phoenix.Controller.html#put_view/2) function: ``` def show(conn, _params) do conn |> put_view(MyAppWeb.SpecialView) |> render(:show, message: "Hello") end ``` [`put_view/2`](Phoenix.Controller.html#put_view/2) can also be used as a plug: ``` defmodule MyAppWeb.UserController do use Phoenix.Controller plug :put_view, html: MyAppWeb.SpecialView def show(conn, _params) do render(conn, :show, message: "Hello") end end ``` ## [](Phoenix.Controller.html#render/3-layouts)Layouts Templates are often rendered inside layouts. By default, Phoenix will render layouts for html requests. For example: ``` defmodule MyAppWeb.UserController do use Phoenix.Controller def show(conn, _params) do render(conn, "show.html", message: "Hello") end end ``` will render the "show.html" template inside an "app.html" template specified in `MyAppWeb.LayoutView`. [`put_layout/2`](Phoenix.Controller.html#put_layout/2) can be used to change the layout, similar to how [`put_view/2`](Phoenix.Controller.html#put_view/2) can be used to change the view. [](Phoenix.Controller.html#root_layout/2) # root\_layout(conn, format \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L814) ``` @spec root_layout(Plug.Conn.t(), binary() | nil) :: {atom(), String.t() | atom()} | false ``` Retrieves the current root layout for the given format. If no format is given, takes the current one from the connection. [](Phoenix.Controller.html#router_module/1) # router\_module(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L334) ``` @spec router_module(Plug.Conn.t()) :: atom() ``` Returns the router module as an atom, raises if unavailable. [](Phoenix.Controller.html#scrub_params/2) # scrub\_params(conn, required\_key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1326) ``` @spec scrub_params(Plug.Conn.t(), String.t()) :: Plug.Conn.t() ``` Scrubs the parameters from the request. This process is two-fold: - Checks to see if the `required_key` is present - Changes empty parameters of `required_key` (recursively) to nils This function is useful for removing empty strings sent via HTML forms. If you are providing an API, there is likely no need to invoke [`scrub_params/2`](Phoenix.Controller.html#scrub_params/2). If the `required_key` is not present, it will raise [`Phoenix.MissingParamError`](Phoenix.MissingParamError.html). ## [](Phoenix.Controller.html#scrub_params/2-examples)Examples ``` iex> scrub_params(conn, "user") ``` [](Phoenix.Controller.html#send_download/3) # send\_download(conn, kind, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1235) Sends the given file or binary as a download. The second argument must be `{:binary, contents}`, where `contents` will be sent as download, or`{:file, path}`, where `path` is the filesystem location of the file to be sent. Be careful to not interpolate the path from external parameters, as it could allow traversal of the filesystem. The download is achieved by setting "content-disposition" to attachment. The "content-type" will also be set based on the extension of the given filename but can be customized via the `:content_type` and `:charset` options. ## [](Phoenix.Controller.html#send_download/3-options)Options - `:filename` - the filename to be presented to the user as download - `:content_type` - the content type of the file or binary sent as download. It is automatically inferred from the filename extension - `:disposition` - specifies disposition type (`:attachment` or `:inline`). If `:attachment` was used, user will be prompted to save the file. If `:inline` was used, the browser will attempt to open the file. Defaults to `:attachment`. - `:charset` - the charset of the file, such as "utf-8". Defaults to none - `:offset` - the bytes to offset when reading. Defaults to `0` - `:length` - the total bytes to read. Defaults to `:all` - `:encode` - encodes the filename using [`URI.encode/2`](../elixir/URI.html#encode/2). Defaults to `true`. When `false`, disables encoding. If you disable encoding, you need to guarantee there are no special characters in the filename, such as quotes, newlines, etc. Otherwise you can expose your application to security attacks ## [](Phoenix.Controller.html#send_download/3-examples)Examples To send a file that is stored inside your application priv directory: ``` path = Application.app_dir(:my_app, "priv/prospectus.pdf") send_download(conn, {:file, path}) ``` When using `{:file, path}`, the filename is inferred from the given path but may also be set explicitly. To allow the user to download contents that are in memory as a binary or string: ``` send_download(conn, {:binary, "world"}, filename: "hello.txt") ``` See [`Plug.Conn.send_file/3`](../plug/1.15.3/Plug.Conn.html#send_file/3) and [`Plug.Conn.send_resp/3`](../plug/1.15.3/Plug.Conn.html#send_resp/3) if you would like to access the low-level functions used to send files and responses via Plug. [](Phoenix.Controller.html#status_message_from_template/1) # status\_message\_from\_template(template) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L1736) Generates a status message from the template name. ## [](Phoenix.Controller.html#status_message_from_template/1-examples)Examples ``` iex> status_message_from_template("404.html") "Not Found" iex> status_message_from_template("whatever.html") "Internal Server Error" ``` [](Phoenix.Controller.html#text/2) # text(conn, data) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L455) ``` @spec text(Plug.Conn.t(), String.Chars.t()) :: Plug.Conn.t() ``` Sends text response. ## [](Phoenix.Controller.html#text/2-examples)Examples ``` iex> text(conn, "hello") iex> text(conn, :implements_to_string) ``` [](Phoenix.Controller.html#view_module/2) # view\_module(conn, format \\\\ nil) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L589) ``` @spec view_module(Plug.Conn.t(), binary() | nil) :: atom() ``` Retrieves the current view for the given format. If no format is given, takes the current one from the connection. [](Phoenix.Controller.html#view_template/1) # view\_template(conn) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/controller.ex#L347) ``` @spec view_template(Plug.Conn.t()) :: binary() | nil ``` Returns the template name rendered in the view as a string (or nil if no template was rendered). [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Digester.Compressor.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/digester/compressor.ex#L1 "View Source") Phoenix.Digester.Compressor behaviour (Phoenix v1.7.18) Defines the [`Phoenix.Digester.Compressor`](Phoenix.Digester.Compressor.html) behaviour for implementing static file compressors. A custom compressor expects 2 functions to be implemented. By default, Phoenix uses only [`Phoenix.Digester.Gzip`](Phoenix.Digester.Gzip.html) to compress static files, but additional compressors can be defined and added to the digest process. ## [](Phoenix.Digester.Compressor.html#module-example)Example If you wanted to compress files using an external brotli compression library, you could define a new module implementing the behaviour and add the module to the list of configured Phoenix static compressors. ``` defmodule MyApp.BrotliCompressor do @behaviour Phoenix.Digester.Compressor def compress_file(file_path, content) do valid_extension = Path.extname(file_path) in Application.fetch_env!(:phoenix, :gzippable_exts) {:ok, compressed_content} = :brotli.encode(content) if valid_extension && byte_size(compressed_content) < byte_size(content) do {:ok, compressed_content} else :error end end def file_extensions do [".br"] end end # config/config.exs config :phoenix, static_compressors: [Phoenix.Digester.Gzip, MyApp.BrotliCompressor], # ... ``` # [](Phoenix.Digester.Compressor.html#summary)Summary ## [Callbacks](Phoenix.Digester.Compressor.html#callbacks) [compress\_file(t, binary)](Phoenix.Digester.Compressor.html#c:compress_file/2) [file\_extensions()](Phoenix.Digester.Compressor.html#c:file_extensions/0) # [](Phoenix.Digester.Compressor.html#callbacks)Callbacks [](Phoenix.Digester.Compressor.html#c:compress_file/2) # compress\_file(t, binary) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/digester/compressor.ex#L42) ``` @callback compress_file(Path.t(), binary()) :: {:ok, binary()} | :error ``` [](Phoenix.Digester.Compressor.html#c:file_extensions/0) # file\_extensions() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/digester/compressor.ex#L43) ``` @callback file_extensions() :: [String.t(), ...] ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Digester.Gzip.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/digester/gzip.ex#L1 "View Source") Phoenix.Digester.Gzip (Phoenix v1.7.18) Gzip compressor for Phoenix.Digester # [](Phoenix.Digester.Gzip.html#summary)Summary ## [Functions](Phoenix.Digester.Gzip.html#functions) [compress\_file(file\_path, content)](Phoenix.Digester.Gzip.html#compress_file/2) Callback implementation for [`Phoenix.Digester.Compressor.compress_file/2`](Phoenix.Digester.Compressor.html#c:compress_file/2). [file\_extensions()](Phoenix.Digester.Gzip.html#file_extensions/0) Callback implementation for [`Phoenix.Digester.Compressor.file_extensions/0`](Phoenix.Digester.Compressor.html#c:file_extensions/0). # [](Phoenix.Digester.Gzip.html#functions)Functions [](Phoenix.Digester.Gzip.html#compress_file/2) # compress\_file(file\_path, content) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/digester/gzip.ex#L7) Callback implementation for [`Phoenix.Digester.Compressor.compress_file/2`](Phoenix.Digester.Compressor.html#c:compress_file/2). [](Phoenix.Digester.Gzip.html#file_extensions/0) # file\_extensions() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/digester/gzip.ex#L15) Callback implementation for [`Phoenix.Digester.Compressor.file_extensions/0`](Phoenix.Digester.Compressor.html#c:file_extensions/0). [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Endpoint.Cowboy2Adapter.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint/cowboy2_adapter.ex#L1 "View Source") Phoenix.Endpoint.Cowboy2Adapter (Phoenix v1.7.18) The Cowboy2 adapter for Phoenix. ## [](Phoenix.Endpoint.Cowboy2Adapter.html#module-endpoint-configuration)Endpoint configuration This adapter uses the following endpoint configuration: - `:http` - the configuration for the HTTP server. It accepts all options as defined by [`Plug.Cowboy`](../plug_cowboy/index.html). Defaults to `false` - `:https` - the configuration for the HTTPS server. It accepts all options as defined by [`Plug.Cowboy`](../plug_cowboy/index.html). Defaults to `false` - `:drainer` - a drainer process that triggers when your application is shutting down to wait for any on-going request to finish. It accepts all options as defined by [`Plug.Cowboy.Drainer`](../plug_cowboy/Plug.Cowboy.Drainer.html). Defaults to `[]`, which will start a drainer process for each configured endpoint, but can be disabled by setting it to `false`. ## [](Phoenix.Endpoint.Cowboy2Adapter.html#module-custom-dispatch-options)Custom dispatch options You can provide custom dispatch options in order to use Phoenix's builtin Cowboy server with custom handlers. For example, to handle raw WebSockets [as shown in Cowboy's docs](https://github.com/ninenines/cowboy/tree/master/examples)). The options are passed to both `:http` and `:https` keys in the endpoint configuration. However, once you pass your custom dispatch options, you will need to manually wire the Phoenix endpoint by adding the following rule: ``` {:_, Plug.Cowboy.Handler, {MyAppWeb.Endpoint, []}} ``` For example: ``` config :myapp, MyAppWeb.Endpoint, http: [dispatch: [ {:_, [ {"/foo", MyAppWeb.CustomHandler, []}, {:_, Plug.Cowboy.Handler, {MyAppWeb.Endpoint, []}} ]}]] ``` It is also important to specify your handlers first, otherwise Phoenix will intercept the requests before they get to your handler. # [](Phoenix.Endpoint.Cowboy2Adapter.html#summary)Summary ## [Functions](Phoenix.Endpoint.Cowboy2Adapter.html#functions) [server\_info(endpoint, scheme)](Phoenix.Endpoint.Cowboy2Adapter.html#server_info/2) # [](Phoenix.Endpoint.Cowboy2Adapter.html#functions)Functions [](Phoenix.Endpoint.Cowboy2Adapter.html#server_info/2) # server\_info(endpoint, scheme) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint/cowboy2_adapter.ex#L141) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Endpoint.SyncCodeReloadPlug.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint/sync_code_reload_plug.ex#L1 "View Source") Phoenix.Endpoint.SyncCodeReloadPlug (Phoenix v1.7.18) Wraps an Endpoint, attempting to sync with Phoenix's code reloader if an exception is raising which indicates that we may be in the middle of a reload. We detect this by looking at the raised exception and seeing if it indicates that the endpoint is not defined. This indicates that the code reloader may be mid way through a compile, and that we should attempt to retry the request after the compile has completed. This is also why this must be implemented in a separate module (one that is not recompiled in a typical code reload cycle), since otherwise it may be the case that the endpoint itself is not defined. # [](Phoenix.Endpoint.SyncCodeReloadPlug.html#summary)Summary ## [Functions](Phoenix.Endpoint.SyncCodeReloadPlug.html#functions) [call(conn, arg)](Phoenix.Endpoint.SyncCodeReloadPlug.html#call/2) Callback implementation for [`Plug.call/2`](../plug/1.15.3/Plug.html#c:call/2). [init(arg)](Phoenix.Endpoint.SyncCodeReloadPlug.html#init/1) Callback implementation for [`Plug.init/1`](../plug/1.15.3/Plug.html#c:init/1). # [](Phoenix.Endpoint.SyncCodeReloadPlug.html#functions)Functions [](Phoenix.Endpoint.SyncCodeReloadPlug.html#call/2) # call(conn, arg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint/sync_code_reload_plug.ex#L18) Callback implementation for [`Plug.call/2`](../plug/1.15.3/Plug.html#c:call/2). [](Phoenix.Endpoint.SyncCodeReloadPlug.html#init/1) # init(arg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint/sync_code_reload_plug.ex#L16) Callback implementation for [`Plug.init/1`](../plug/1.15.3/Plug.html#c:init/1). [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Endpoint.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L1 "View Source") Phoenix.Endpoint behaviour (Phoenix v1.7.18) Defines a Phoenix endpoint. The endpoint is the boundary where all requests to your web application start. It is also the interface your application provides to the underlying web servers. Overall, an endpoint has three responsibilities: - to provide a wrapper for starting and stopping the endpoint as part of a supervision tree - to define an initial plug pipeline for requests to pass through - to host web specific configuration for your application ## [](Phoenix.Endpoint.html#module-endpoints)Endpoints An endpoint is simply a module defined with the help of [`Phoenix.Endpoint`](Phoenix.Endpoint.html). If you have used the [`mix phx.new`](Mix.Tasks.Phx.New.html) generator, an endpoint was automatically generated as part of your application: ``` defmodule YourAppWeb.Endpoint do use Phoenix.Endpoint, otp_app: :your_app # plug ... # plug ... plug YourApp.Router end ``` Endpoints must be explicitly started as part of your application supervision tree. Endpoints are added by default to the supervision tree in generated applications. Endpoints can be added to the supervision tree as follows: ``` children = [ YourAppWeb.Endpoint ] ``` ## [](Phoenix.Endpoint.html#module-endpoint-configuration)Endpoint configuration All endpoints are configured in your application environment. For example: ``` config :your_app, YourAppWeb.Endpoint, secret_key_base: "kjoy3o1zeidquwy1398juxzldjlksahdk3" ``` Endpoint configuration is split into two categories. Compile-time configuration means the configuration is read during compilation and changing it at runtime has no effect. The compile-time configuration is mostly related to error handling. Runtime configuration, instead, is accessed during or after your application is started and can be read through the [`config/2`](Phoenix.Endpoint.html#c:config/2) function: ``` YourAppWeb.Endpoint.config(:port) YourAppWeb.Endpoint.config(:some_config, :default_value) ``` ### [](Phoenix.Endpoint.html#module-compile-time-configuration)Compile-time configuration Compile-time configuration may be set on `config/dev.exs`, `config/prod.exs` and so on, but has no effect on `config/runtime.exs`: - `:code_reloader` - when `true`, enables code reloading functionality. For the list of code reloader configuration options see [`Phoenix.CodeReloader.reload/1`](Phoenix.CodeReloader.html#reload/1). Keep in mind code reloading is based on the file-system, therefore it is not possible to run two instances of the same app at the same time with code reloading in development, as they will race each other and only one will effectively recompile the files. In such cases, tweak your config files so code reloading is enabled in only one of the apps or set the MIX\_BUILD environment variable to give them distinct build directories - `:debug_errors` - when `true`, uses [`Plug.Debugger`](../plug/1.15.3/Plug.Debugger.html) functionality for debugging failures in the application. Recommended to be set to `true` only in development as it allows listing of the application source code during debugging. Defaults to `false` - `:force_ssl` - ensures no data is ever sent via HTTP, always redirecting to HTTPS. It expects a list of options which are forwarded to [`Plug.SSL`](../plug/1.15.3/Plug.SSL.html). By default it sets the "strict-transport-security" header in HTTPS requests, forcing browsers to always use HTTPS. If an unsafe request (HTTP) is sent, it redirects to the HTTPS version using the `:host` specified in the `:url` configuration. To dynamically redirect to the `host` of the current request, set `:host` in the `:force_ssl` configuration to `nil` ### [](Phoenix.Endpoint.html#module-runtime-configuration)Runtime configuration The configuration below may be set on `config/dev.exs`, `config/prod.exs` and so on, as well as on `config/runtime.exs`. Typically, if you need to configure them with system environment variables, you set them in `config/runtime.exs`. These options may also be set when starting the endpoint in your supervision tree, such as `{MyApp.Endpoint, options}`. - `:adapter` - which webserver adapter to use for serving web requests. See the "Adapter configuration" section below - `:cache_static_manifest` - a path to a json manifest file that contains static files and their digested version. This is typically set to "priv/static/cache\_manifest.json" which is the file automatically generated by [`mix phx.digest`](Mix.Tasks.Phx.Digest.html). It can be either: a string containing a file system path or a tuple containing the application name and the path within that application. - `:cache_static_manifest_latest` - a map of the static files pointing to their digest version. This is automatically loaded from `cache_static_manifest` on boot. However, if you have your own static handling mechanism, you may want to set this value explicitly. This is used by projects such as `LiveView` to detect if the client is running on the latest version of all assets. - `:cache_manifest_skip_vsn` - when true, skips the appended query string "?vsn=d" when generating paths to static assets. This query string is used by [`Plug.Static`](../plug/1.15.3/Plug.Static.html) to set long expiry dates, therefore, you should set this option to true only if you are not using [`Plug.Static`](../plug/1.15.3/Plug.Static.html) to serve assets, for example, if you are using a CDN. If you are setting this option, you should also consider passing `--no-vsn` to [`mix phx.digest`](Mix.Tasks.Phx.Digest.html). Defaults to `false`. - `:check_origin` - configure the default `:check_origin` setting for transports. See [`socket/3`](Phoenix.Endpoint.html#socket/3) for options. Defaults to `true`. - `:secret_key_base` - a secret key used as a base to generate secrets for encrypting and signing data. For example, cookies and tokens are signed by default, but they may also be encrypted if desired. Defaults to `nil` as it must be set per application - `:server` - when `true`, starts the web server when the endpoint supervision tree starts. Defaults to `false`. The [`mix phx.server`](Mix.Tasks.Phx.Server.html) task automatically sets this to `true` - `:url` - configuration for generating URLs throughout the app. Accepts the `:host`, `:scheme`, `:path` and `:port` options. All keys except `:path` can be changed at runtime. Defaults to: ``` [host: "localhost", path: "/"] ``` The `:port` option requires either an integer or string. The `:host` option requires a string. The `:scheme` option accepts `"http"` and `"https"` values. Default value is inferred from top level `:http` or `:https` option. It is useful when hosting Phoenix behind a load balancer or reverse proxy and terminating SSL there. The `:path` option can be used to override root path. Useful when hosting Phoenix behind a reverse proxy with URL rewrite rules - `:static_url` - configuration for generating URLs for static files. It will fallback to `url` if no option is provided. Accepts the same options as `url` - `:watchers` - a set of watchers to run alongside your server. It expects a list of tuples containing the executable and its arguments. Watchers are guaranteed to run in the application directory, but only when the server is enabled (unless `:force_watchers` configuration is set to `true`). For example, the watcher below will run the "watch" mode of the webpack build tool when the server starts. You can configure it to whatever build tool or command you want: ``` [ node: [ "node_modules/webpack/bin/webpack.js", "--mode", "development", "--watch", "--watch-options-stdin" ] ] ``` The `:cd` and `:env` options can be given at the end of the list to customize the watcher: ``` [node: [..., cd: "assets", env: [{"TAILWIND_MODE", "watch"}]]] ``` A watcher can also be a module-function-args tuple that will be invoked accordingly: ``` [another: {Mod, :fun, [arg1, arg2]}] ``` - `:force_watchers` - when `true`, forces your watchers to start even when the `:server` option is set to `false`. - `:live_reload` - configuration for the live reload option. Configuration requires a `:patterns` option which should be a list of file patterns to watch. When these files change, it will trigger a reload. ``` live_reload: [ url: "ws://localhost:4000", patterns: [ ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", ~r"lib/app_web/(live|views)/.*(ex)$", ~r"lib/app_web/templates/.*(eex)$" ] ] ``` - `:pubsub_server` - the name of the pubsub server to use in channels and via the Endpoint broadcast functions. The PubSub server is typically started in your supervision tree. - `:render_errors` - responsible for rendering templates whenever there is a failure in the application. For example, if the application crashes with a 500 error during a HTML request, `render("500.html", assigns)` will be called in the view given to `:render_errors`. A `:formats` list can be provided to specify a module per format to handle error rendering. Example: ``` [formats: [html: MyApp.ErrorHTML], layout: false, log: :debug] ``` - `:log_access_url` - log the access url once the server boots Note that you can also store your own configurations in the Phoenix.Endpoint. For example, [Phoenix LiveView](../phoenix_live_view.html) expects its own configuration under the `:live_view` key. In such cases, you should consult the documentation of the respective projects. ### [](Phoenix.Endpoint.html#module-adapter-configuration)Adapter configuration Phoenix allows you to choose which webserver adapter to use. Newly generated applications created via the `phx.new` Mix task use the [`Bandit`](https://github.com/mtrudel/bandit) webserver via the `Bandit.PhoenixAdapter` adapter. If not otherwise specified via the `adapter` option Phoenix will fall back to the [`Phoenix.Endpoint.Cowboy2Adapter`](Phoenix.Endpoint.Cowboy2Adapter.html) for backwards compatibility with applications generated prior to Phoenix 1.7.8. Both adapters can be configured in a similar manner using the following two top-level options: - `:http` - the configuration for the HTTP server. It accepts all options as defined by either [`Bandit`](../bandit/Bandit.html#t:options/0) or [`Plug.Cowboy`](../plug_cowboy/index.html) depending on your choice of adapter. Defaults to `false` - `:https` - the configuration for the HTTPS server. It accepts all options as defined by either [`Bandit`](../bandit/Bandit.html#t:options/0) or [`Plug.Cowboy`](../plug_cowboy/index.html) depending on your choice of adapter. Defaults to `false` In addition, the connection draining can be configured for the Cowboy webserver via the following top-level option (this is not required for Bandit as it has connection draining built-in): - `:drainer` - a drainer process waits for any on-going request to finish during application shutdown. It accepts the `:shutdown` and `:check_interval` options as defined by [`Plug.Cowboy.Drainer`](../plug_cowboy/2.7.0/Plug.Cowboy.Drainer.html). Note the draining does not terminate any existing connection, it simply waits for them to finish. Socket connections run their own drainer before this one is invoked. That's because sockets are stateful and can be gracefully notified, which allows us to stagger them over a longer period of time. See the documentation for [`socket/3`](Phoenix.Endpoint.html#socket/3) for more information ## [](Phoenix.Endpoint.html#module-endpoint-api)Endpoint API In the previous section, we have used the [`config/2`](Phoenix.Endpoint.html#c:config/2) function that is automatically generated in your endpoint. Here's a list of all the functions that are automatically defined in your endpoint: - for handling paths and URLs: [`struct_url/0`](Phoenix.Endpoint.html#c:struct_url/0), [`url/0`](Phoenix.Endpoint.html#c:url/0), [`path/1`](Phoenix.Endpoint.html#c:path/1), [`static_url/0`](Phoenix.Endpoint.html#c:static_url/0),[`static_path/1`](Phoenix.Endpoint.html#c:static_path/1), and [`static_integrity/1`](Phoenix.Endpoint.html#c:static_integrity/1) - for gathering runtime information about the address and port the endpoint is running on: [`server_info/1`](Phoenix.Endpoint.html#c:server_info/1) - for broadcasting to channels: [`broadcast/3`](Phoenix.Endpoint.html#c:broadcast/3), [`broadcast!/3`](Phoenix.Endpoint.html#c:broadcast!/3), [`broadcast_from/4`](Phoenix.Endpoint.html#c:broadcast_from/4), [`broadcast_from!/4`](Phoenix.Endpoint.html#c:broadcast_from!/4), [`local_broadcast/3`](Phoenix.Endpoint.html#c:local_broadcast/3), and [`local_broadcast_from/4`](Phoenix.Endpoint.html#c:local_broadcast_from/4) - for configuration: [`start_link/1`](Phoenix.Endpoint.html#c:start_link/1), [`config/2`](Phoenix.Endpoint.html#c:config/2), and [`config_change/2`](Phoenix.Endpoint.html#c:config_change/2) - as required by the [`Plug`](../plug/1.15.3/Plug.html) behaviour: [`Plug.init/1`](../plug/1.15.3/Plug.html#c:init/1) and [`Plug.call/2`](../plug/1.15.3/Plug.html#c:call/2) # [](Phoenix.Endpoint.html#summary)Summary ## [Types](Phoenix.Endpoint.html#types) [event()](Phoenix.Endpoint.html#t:event/0) [msg()](Phoenix.Endpoint.html#t:msg/0) [topic()](Phoenix.Endpoint.html#t:topic/0) ## [Callbacks](Phoenix.Endpoint.html#callbacks) [broadcast(topic, event, msg)](Phoenix.Endpoint.html#c:broadcast/3) Broadcasts a `msg` as `event` in the given `topic` to all nodes. [broadcast!(topic, event, msg)](Phoenix.Endpoint.html#c:broadcast!/3) Broadcasts a `msg` as `event` in the given `topic` to all nodes. [broadcast\_from(from, topic, event, msg)](Phoenix.Endpoint.html#c:broadcast_from/4) Broadcasts a `msg` from the given `from` as `event` in the given `topic` to all nodes. [broadcast\_from!(from, topic, event, msg)](Phoenix.Endpoint.html#c:broadcast_from!/4) Broadcasts a `msg` from the given `from` as `event` in the given `topic` to all nodes. [config key, default](Phoenix.Endpoint.html#c:config/2) Access the endpoint configuration given by key. [config\_change(changed, removed)](Phoenix.Endpoint.html#c:config_change/2) Reload the endpoint configuration on application upgrades. [host()](Phoenix.Endpoint.html#c:host/0) Returns the host from the :url configuration. [local\_broadcast(topic, event, msg)](Phoenix.Endpoint.html#c:local_broadcast/3) Broadcasts a `msg` as `event` in the given `topic` within the current node. [local\_broadcast\_from(from, topic, event, msg)](Phoenix.Endpoint.html#c:local_broadcast_from/4) Broadcasts a `msg` from the given `from` as `event` in the given `topic` within the current node. [path(path)](Phoenix.Endpoint.html#c:path/1) Generates the path information when routing to this endpoint. [script\_name()](Phoenix.Endpoint.html#c:script_name/0) Returns the script name from the :url configuration. [server\_info(scheme)](Phoenix.Endpoint.html#c:server_info/1) Returns the address and port that the server is running on [start\_link(keyword)](Phoenix.Endpoint.html#c:start_link/1) Starts the endpoint supervision tree. [static\_integrity(path)](Phoenix.Endpoint.html#c:static_integrity/1) Generates an integrity hash to a static file in `priv/static`. [static\_lookup(path)](Phoenix.Endpoint.html#c:static_lookup/1) Generates a two item tuple containing the `static_path` and `static_integrity`. [static\_path(path)](Phoenix.Endpoint.html#c:static_path/1) Generates a route to a static file in `priv/static`. [static\_url()](Phoenix.Endpoint.html#c:static_url/0) Generates the static URL without any path information. [struct\_url()](Phoenix.Endpoint.html#c:struct_url/0) Generates the endpoint base URL, but as a [`URI`](../elixir/URI.html) struct. [subscribe(topic, opts)](Phoenix.Endpoint.html#c:subscribe/2) Subscribes the caller to the given topic. [unsubscribe(topic)](Phoenix.Endpoint.html#c:unsubscribe/1) Unsubscribes the caller from the given topic. [url()](Phoenix.Endpoint.html#c:url/0) Generates the endpoint base URL without any path information. ## [Functions](Phoenix.Endpoint.html#functions) [server?(otp\_app, endpoint)](Phoenix.Endpoint.html#server?/2) Checks if Endpoint's web server has been configured to start. [socket(path, module, opts \\\\ \[\])](Phoenix.Endpoint.html#socket/3) Defines a websocket/longpoll mount-point for a `socket`. # [](Phoenix.Endpoint.html#types)Types [](Phoenix.Endpoint.html#t:event/0) # event() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L278) ``` @type event() :: String.t() ``` [](Phoenix.Endpoint.html#t:msg/0) # msg() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L279) ``` @type msg() :: map() | {:binary, binary()} ``` [](Phoenix.Endpoint.html#t:topic/0) # topic() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L277) ``` @type topic() :: String.t() ``` # [](Phoenix.Endpoint.html#callbacks)Callbacks [](Phoenix.Endpoint.html#c:broadcast/3) # broadcast(topic, event, msg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L376) ``` @callback broadcast(topic(), event(), msg()) :: :ok | {:error, term()} ``` Broadcasts a `msg` as `event` in the given `topic` to all nodes. [](Phoenix.Endpoint.html#c:broadcast!/3) # broadcast!(topic, event, msg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L383) ``` @callback broadcast!(topic(), event(), msg()) :: :ok ``` Broadcasts a `msg` as `event` in the given `topic` to all nodes. Raises in case of failures. [](Phoenix.Endpoint.html#c:broadcast_from/4) # broadcast\_from(from, topic, event, msg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L388) ``` @callback broadcast_from(from :: pid(), topic(), event(), msg()) :: :ok | {:error, term()} ``` Broadcasts a `msg` from the given `from` as `event` in the given `topic` to all nodes. [](Phoenix.Endpoint.html#c:broadcast_from!/4) # broadcast\_from!(from, topic, event, msg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L395) ``` @callback broadcast_from!(from :: pid(), topic(), event(), msg()) :: :ok ``` Broadcasts a `msg` from the given `from` as `event` in the given `topic` to all nodes. Raises in case of failures. [](Phoenix.Endpoint.html#c:config/2) # config key, default [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L296) ``` @callback config(key :: atom(), default :: term()) :: term() ``` Access the endpoint configuration given by key. [](Phoenix.Endpoint.html#c:config_change/2) # config\_change(changed, removed) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L301) ``` @callback config_change(changed :: term(), removed :: term()) :: term() ``` Reload the endpoint configuration on application upgrades. [](Phoenix.Endpoint.html#c:host/0) # host() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L348) ``` @callback host() :: String.t() ``` Returns the host from the :url configuration. [](Phoenix.Endpoint.html#c:local_broadcast/3) # local\_broadcast(topic, event, msg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L400) ``` @callback local_broadcast(topic(), event(), msg()) :: :ok ``` Broadcasts a `msg` as `event` in the given `topic` within the current node. [](Phoenix.Endpoint.html#c:local_broadcast_from/4) # local\_broadcast\_from(from, topic, event, msg) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L405) ``` @callback local_broadcast_from(from :: pid(), topic(), event(), msg()) :: :ok ``` Broadcasts a `msg` from the given `from` as `event` in the given `topic` within the current node. [](Phoenix.Endpoint.html#c:path/1) # path(path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L318) ``` @callback path(path :: String.t()) :: String.t() ``` Generates the path information when routing to this endpoint. [](Phoenix.Endpoint.html#c:script_name/0) # script\_name() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L343) ``` @callback script_name() :: [String.t()] ``` Returns the script name from the :url configuration. [](Phoenix.Endpoint.html#c:server_info/1) # server\_info(scheme) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L355) ``` @callback server_info(Plug.Conn.scheme()) :: {:ok, {:inet.ip_address(), :inet.port_number()} | :inet.returned_non_ip_address()} | {:error, term()} ``` Returns the address and port that the server is running on [](Phoenix.Endpoint.html#c:start_link/1) # start\_link(keyword) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L291) ``` @callback start_link(keyword()) :: Supervisor.on_start() ``` Starts the endpoint supervision tree. Starts endpoint's configuration cache and possibly the servers for handling requests. [](Phoenix.Endpoint.html#c:static_integrity/1) # static\_integrity(path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L333) ``` @callback static_integrity(path :: String.t()) :: String.t() | nil ``` Generates an integrity hash to a static file in `priv/static`. [](Phoenix.Endpoint.html#c:static_lookup/1) # static\_lookup(path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L338) ``` @callback static_lookup(path :: String.t()) :: {String.t(), String.t()} | {String.t(), nil} ``` Generates a two item tuple containing the `static_path` and `static_integrity`. [](Phoenix.Endpoint.html#c:static_path/1) # static\_path(path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L328) ``` @callback static_path(path :: String.t()) :: String.t() ``` Generates a route to a static file in `priv/static`. [](Phoenix.Endpoint.html#c:static_url/0) # static\_url() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L323) ``` @callback static_url() :: String.t() ``` Generates the static URL without any path information. [](Phoenix.Endpoint.html#c:struct_url/0) # struct\_url() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L308) ``` @callback struct_url() :: URI.t() ``` Generates the endpoint base URL, but as a [`URI`](../elixir/URI.html) struct. [](Phoenix.Endpoint.html#c:subscribe/2) # subscribe(topic, opts) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L366) ``` @callback subscribe(topic(), opts :: Keyword.t()) :: :ok | {:error, term()} ``` Subscribes the caller to the given topic. See [`Phoenix.PubSub.subscribe/3`](../phoenix_pubsub/2.1.3/Phoenix.PubSub.html#subscribe/3) for options. [](Phoenix.Endpoint.html#c:unsubscribe/1) # unsubscribe(topic) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L371) ``` @callback unsubscribe(topic()) :: :ok | {:error, term()} ``` Unsubscribes the caller from the given topic. [](Phoenix.Endpoint.html#c:url/0) # url() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L313) ``` @callback url() :: String.t() ``` Generates the endpoint base URL without any path information. # [](Phoenix.Endpoint.html#functions)Functions [](Phoenix.Endpoint.html#server?/2) # server?(otp\_app, endpoint) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L1014) Checks if Endpoint's web server has been configured to start. - `otp_app` - The OTP app running the endpoint, for example `:my_app` - `endpoint` - The endpoint module, for example `MyAppWeb.Endpoint` ## [](Phoenix.Endpoint.html#server?/2-examples)Examples ``` iex> Phoenix.Endpoint.server?(:my_app, MyAppWeb.Endpoint) true ``` [](Phoenix.Endpoint.html#socket/3) # socket(path, module, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/endpoint.ex#L988)(macro) Defines a websocket/longpoll mount-point for a `socket`. It expects a `path`, a `socket` module, and a set of options. The socket module is typically defined with [`Phoenix.Socket`](Phoenix.Socket.html). Both websocket and longpolling connections are supported out of the box. ## [](Phoenix.Endpoint.html#socket/3-options)Options - `:websocket` - controls the websocket configuration. Defaults to `true`. May be false or a keyword list of options. See ["Common configuration"](Phoenix.Endpoint.html#socket/3-common-configuration) and ["WebSocket configuration"](Phoenix.Endpoint.html#socket/3-websocket-configuration) for the whole list - `:longpoll` - controls the longpoll configuration. Defaults to `false`. May be true or a keyword list of options. See ["Common configuration"](Phoenix.Endpoint.html#socket/3-common-configuration) and ["Longpoll configuration"](Phoenix.Endpoint.html#socket/3-longpoll-configuration) for the whole list - `:drainer` - a keyword list or a custom MFA function returning a keyword list, for example: ``` {MyAppWeb.Socket, :drainer_configuration, []} ``` configuring how to drain sockets on application shutdown. The goal is to notify all channels (and LiveViews) clients to reconnect. The supported options are: - `:batch_size` - How many clients to notify at once in a given batch. Defaults to 10000. - `:batch_interval` - The amount of time in milliseconds given for a batch to terminate. Defaults to 2000ms. - `:shutdown` - The maximum amount of time in milliseconds allowed to drain all batches. Defaults to 30000ms. For example, if you have 150k connections, the default values will split them into 15 batches of 10k connections. Each batch takes 2000ms before the next batch starts. In this case, we will do everything right under the maximum shutdown time of 30000ms. Therefore, as you increase the number of connections, remember to adjust the shutdown accordingly. Finally, after the socket drainer runs, the lower level HTTP/HTTPS connection drainer will still run, and apply to all connections. Set it to `false` to disable draining. You can also pass the options below on `use Phoenix.Socket`. The values specified here override the value in `use Phoenix.Socket`. ## [](Phoenix.Endpoint.html#socket/3-examples)Examples ``` socket "/ws", MyApp.UserSocket socket "/ws/admin", MyApp.AdminUserSocket, longpoll: true, websocket: [compress: true] ``` ## [](Phoenix.Endpoint.html#socket/3-path-params)Path params It is possible to include variables in the path, these will be available in the `params` that are passed to the socket. ``` socket "/ws/:user_id", MyApp.UserSocket, websocket: [path: "/project/:project_id"] ``` ## [](Phoenix.Endpoint.html#socket/3-common-configuration)Common configuration The configuration below can be given to both `:websocket` and `:longpoll` keys: - `:path` - the path to use for the transport. Will default to the transport name ("/websocket" or "/longpoll") - `:serializer` - a list of serializers for messages. See [`Phoenix.Socket`](Phoenix.Socket.html) for more information - `:transport_log` - if the transport layer itself should log and, if so, the level - `:check_origin` - if the transport should check the origin of requests when the `origin` header is present. May be `true`, `false`, a list of hosts that are allowed, or a function provided as MFA tuple. Defaults to `:check_origin` setting at endpoint configuration. If `true`, the header is checked against `:host` in `YourAppWeb.Endpoint.config(:url)[:host]`. If `false` and you do not validate the session in your socket, your app is vulnerable to Cross-Site WebSocket Hijacking (CSWSH) attacks. Only use in development, when the host is truly unknown or when serving clients that do not send the `origin` header, such as mobile apps. You can also specify a list of explicitly allowed origins. Wildcards are supported. ``` check_origin: [ "https://example.com", "//another.com:888", "//*.other.com" ] ``` Or to accept any origin matching the request connection's host, port, and scheme: ``` check_origin: :conn ``` Or a custom MFA function: ``` check_origin: {MyAppWeb.Auth, :my_check_origin?, []} ``` The MFA is invoked with the request `%URI{}` as the first argument, followed by arguments in the MFA list, and must return a boolean. - `:code_reloader` - enable or disable the code reloader. Defaults to your endpoint configuration - `:connect_info` - a list of keys that represent data to be copied from the transport to be made available in the user socket `connect/3` callback. See the "Connect info" subsection for valid keys ### [](Phoenix.Endpoint.html#socket/3-connect-info)Connect info The valid keys are: - `:peer_data` - the result of [`Plug.Conn.get_peer_data/1`](../plug/1.15.3/Plug.Conn.html#get_peer_data/1) - `:trace_context_headers` - a list of all trace context headers. Supported headers are defined by the [W3C Trace Context Specification](https://www.w3.org/TR/trace-context-1/). These headers are necessary for libraries such as [OpenTelemetry](https://opentelemetry.io/) to extract trace propagation information to know this request is part of a larger trace in progress. - `:x_headers` - all request headers that have an "x-" prefix - `:uri` - a `%URI{}` with information from the conn - `:user_agent` - the value of the "user-agent" request header - `{:session, session_config}` - the session information from [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html). The `session_config` is typically an exact copy of the arguments given to [`Plug.Session`](../plug/1.15.3/Plug.Session.html). In order to validate the session, the "\_csrf\_token" must be given as request parameter when connecting the socket with the value of `URI.encode_www_form(Plug.CSRFProtection.get_csrf_token())`. The CSRF token request parameter can be modified via the `:csrf_token_key` option. Additionally, `session_config` may be a MFA, such as `{MyAppWeb.Auth, :get_session_config, []}`, to allow loading config in runtime. Arbitrary keywords may also appear following the above valid keys, which is useful for passing custom connection information to the socket. For example: ``` socket "/socket", AppWeb.UserSocket, websocket: [ connect_info: [:peer_data, :trace_context_headers, :x_headers, :uri, session: [store: :cookie]] ] ``` With arbitrary keywords: ``` socket "/socket", AppWeb.UserSocket, websocket: [ connect_info: [:uri, custom_value: "abcdef"] ] ``` #### Where are my headers? Phoenix only gives you limited access to the connection headers for security reasons. WebSockets are cross-domain, which means that, when a user "John Doe" visits a malicious website, the malicious website can open up a WebSocket connection to your application, and the browser will gladly submit John Doe's authentication/cookie information. If you were to accept this information as is, the malicious website would have full control of a WebSocket connection to your application, authenticated on John Doe's behalf. To safe-guard your application, Phoenix limits and validates the connection information your socket can access. This means your application is safe from these attacks, but you can't access cookies and other headers in your socket. You may access the session stored in the connection via the `:connect_info` option, provided you also pass a csrf token when connecting over WebSocket. ## [](Phoenix.Endpoint.html#socket/3-websocket-configuration)Websocket configuration The following configuration applies only to `:websocket`. - `:timeout` - the timeout for keeping websocket connections open after it last received data, defaults to 60\_000ms - `:max_frame_size` - the maximum allowed frame size in bytes, defaults to "infinity" - `:fullsweep_after` - the maximum number of garbage collections before forcing a fullsweep for the socket process. You can set it to `0` to force more frequent cleanups of your websocket transport processes. Setting this option requires Erlang/OTP 24 - `:compress` - whether to enable per message compression on all data frames, defaults to false - `:subprotocols` - a list of supported websocket subprotocols. Used for handshake `Sec-WebSocket-Protocol` response header, defaults to nil. For example: ``` subprotocols: ["sip", "mqtt"] ``` - `:error_handler` - custom error handler for connection errors. If [`Phoenix.Socket.connect/3`](Phoenix.Socket.html#c:connect/3) returns an `{:error, reason}` tuple, the error handler will be called with the error reason. For WebSockets, the error handler must be a MFA tuple that receives a [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html), the error reason, and returns a [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) with a response. For example: ``` socket "/socket", MySocket, websocket: [ error_handler: {MySocket, :handle_error, []} ] ``` and a `{:error, :rate_limit}` return may be handled on `MySocket` as: ``` def handle_error(conn, :rate_limit), do: Plug.Conn.send_resp(conn, 429, "Too many requests") ``` ## [](Phoenix.Endpoint.html#socket/3-longpoll-configuration)Longpoll configuration The following configuration applies only to `:longpoll`: - `:window_ms` - how long the client can wait for new messages in its poll request in milliseconds (ms). Defaults to `10_000`. - `:pubsub_timeout_ms` - how long a request can wait for the pubsub layer to respond in milliseconds (ms). Defaults to `2000`. - `:crypto` - options for verifying and signing the token, accepted by [`Phoenix.Token`](Phoenix.Token.html). By default tokens are valid for 2 weeks [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Flash.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/flash.ex#L1 "View Source") Phoenix.Flash (Phoenix v1.7.18) Provides shared flash access. # [](Phoenix.Flash.html#summary)Summary ## [Functions](Phoenix.Flash.html#functions) [get(flash, key)](Phoenix.Flash.html#get/2) Gets the key from the map of flash data. # [](Phoenix.Flash.html#functions)Functions [](Phoenix.Flash.html#get/2) # get(flash, key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/flash.ex#L14) Gets the key from the map of flash data. ## [](Phoenix.Flash.html#get/2-examples)Examples ``` <div id="info"><%= Phoenix.Flash.get(@flash, :info) %></div> <div id="error"><%= Phoenix.Flash.get(@flash, :error) %></div> ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Logger.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/logger.ex#L1 "View Source") Phoenix.Logger (Phoenix v1.7.18) Instrumenter to handle logging of various instrumentation events. ## [](Phoenix.Logger.html#module-instrumentation)Instrumentation Phoenix uses the `:telemetry` library for instrumentation. The following events are published by Phoenix with the following measurements and metadata: - `[:phoenix, :endpoint, :init]` - dispatched by [`Phoenix.Endpoint`](Phoenix.Endpoint.html) after your Endpoint supervision tree successfully starts - Measurement: `%{system_time: system_time}` - Metadata: `%{pid: pid(), config: Keyword.t(), module: module(), otp_app: atom()}` - Disable logging: This event is not logged - `[:phoenix, :endpoint, :start]` - dispatched by [`Plug.Telemetry`](../plug/1.15.3/Plug.Telemetry.html) in your endpoint, usually after code reloading - Measurement: `%{system_time: system_time}` - Metadata: `%{conn: Plug.Conn.t, options: Keyword.t}` - Options: `%{log: Logger.level | false}` - Disable logging: In your endpoint `plug Plug.Telemetry, ..., log: Logger.level | false` - Configure log level dynamically: `plug Plug.Telemetry, ..., log: {Mod, Fun, Args}` - `[:phoenix, :endpoint, :stop]` - dispatched by [`Plug.Telemetry`](../plug/1.15.3/Plug.Telemetry.html) in your endpoint whenever the response is sent - Measurement: `%{duration: native_time}` - Metadata: `%{conn: Plug.Conn.t, options: Keyword.t}` - Options: `%{log: Logger.level | false}` - Disable logging: In your endpoint `plug Plug.Telemetry, ..., log: Logger.level | false` - Configure log level dynamically: `plug Plug.Telemetry, ..., log: {Mod, Fun, Args}` - `[:phoenix, :router_dispatch, :start]` - dispatched by [`Phoenix.Router`](Phoenix.Router.html) before dispatching to a matched route - Measurement: `%{system_time: System.system_time}` - Metadata: `%{conn: Plug.Conn.t, route: binary, plug: module, plug_opts: term, path_params: map, pipe_through: [atom], log: Logger.level | false}` - Disable logging: Pass `log: false` to the router macro, for example: `get("/page", PageController, :index, log: false)` - Configure log level dynamically: `get("/page", PageController, :index, log: {Mod, Fun, Args})` - `[:phoenix, :router_dispatch, :exception]` - dispatched by [`Phoenix.Router`](Phoenix.Router.html) after exceptions on dispatching a route - Measurement: `%{duration: native_time}` - Metadata: `%{conn: Plug.Conn.t, kind: :throw | :error | :exit, reason: term(), stacktrace: Exception.stacktrace()}` - Disable logging: This event is not logged - `[:phoenix, :router_dispatch, :stop]` - dispatched by [`Phoenix.Router`](Phoenix.Router.html) after successfully dispatching a matched route - Measurement: `%{duration: native_time}` - Metadata: `%{conn: Plug.Conn.t, route: binary, plug: module, plug_opts: term, path_params: map, pipe_through: [atom], log: Logger.level | false}` - Disable logging: This event is not logged - `[:phoenix, :error_rendered]` - dispatched at the end of an error view being rendered - Measurement: `%{duration: native_time}` - Metadata: `%{conn: Plug.Conn.t, status: Plug.Conn.status, kind: Exception.kind, reason: term, stacktrace: Exception.stacktrace}` - Disable logging: Set `render_errors: [log: false]` on your endpoint configuration - `[:phoenix, :socket_connected]` - dispatched by [`Phoenix.Socket`](Phoenix.Socket.html), at the end of a socket connection - Measurement: `%{duration: native_time}` - Metadata: `%{endpoint: atom, transport: atom, params: term, connect_info: map, vsn: binary, user_socket: atom, result: :ok | :error, serializer: atom, log: Logger.level | false}` - Disable logging: `use Phoenix.Socket, log: false` or `socket "/foo", MySocket, websocket: [log: false]` in your endpoint - `[:phoenix, :channel_joined]` - dispatched at the end of a channel join - Measurement: `%{duration: native_time}` - Metadata: `%{result: :ok | :error, params: term, socket: Phoenix.Socket.t}` - Disable logging: This event cannot be disabled - `[:phoenix, :channel_handled_in]` - dispatched at the end of a channel handle in - Measurement: `%{duration: native_time}` - Metadata: `%{event: binary, params: term, socket: Phoenix.Socket.t}` - Disable logging: This event cannot be disabled To see an example of how Phoenix LiveDashboard uses these events to create metrics, visit [https://hexdocs.pm/phoenix\_live\_dashboard/metrics.html](../phoenix_live_dashboard/metrics.html). ## [](Phoenix.Logger.html#module-parameter-filtering)Parameter filtering When logging parameters, Phoenix can filter out sensitive parameters such as passwords and tokens. Parameters to be filtered can be added via the `:filter_parameters` option: ``` config :phoenix, :filter_parameters, ["password", "secret"] ``` With the configuration above, Phoenix will filter any parameter that contains the terms `password` or `secret`. The match is case sensitive. Phoenix's default is `["password"]`. Phoenix can filter all parameters by default and selectively keep parameters. This can be configured like so: ``` config :phoenix, :filter_parameters, {:keep, ["id", "order"]} ``` With the configuration above, Phoenix will filter all parameters, except those that match exactly `id` or `order`. If a kept parameter matches, all parameters nested under that one will also be kept. ## [](Phoenix.Logger.html#module-dynamic-log-level)Dynamic log level In some cases you may wish to set the log level dynamically on a per-request basis. To do so, set the `:log` option to a tuple, `{Mod, Fun, Args}`. The `Plug.Conn.t()` for the request will be prepended to the provided list of arguments. When invoked, your function must return a [`Logger.level()`](../logger/Logger.html#t:level/0) or `false` to disable logging for the request. For example, in your Endpoint you might do something like this: ``` # lib/my_app_web/endpoint.ex plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint], log: {__MODULE__, :log_level, []} # Disables logging for routes like /status/* def log_level(%{path_info: ["status" | _]}), do: false def log_level(_), do: :info ``` ## [](Phoenix.Logger.html#module-disabling)Disabling When you are using custom logging system it is not always desirable to enable [`Phoenix.Logger`](Phoenix.Logger.html) by default. You can always disable this in general by: ``` config :phoenix, :logger, false ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.MissingParamError.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/exceptions.ex#L18 "View Source") Phoenix.MissingParamError exception (Phoenix v1.7.18) Raised when a key is expected to be present in the request parameters, but is not. This exception is raised by [`Phoenix.Controller.scrub_params/2`](Phoenix.Controller.html#scrub_params/2) which: - Checks to see if the required\_key is present (can be empty) - Changes all empty parameters to nils ("" -&gt; nil) If you are seeing this error, you should handle the error and surface it to the end user. It means that there is a parameter missing from the request. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Naming.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/naming.ex#L1 "View Source") Phoenix.Naming (Phoenix v1.7.18) Conveniences for inflecting and working with names in Phoenix. # [](Phoenix.Naming.html#summary)Summary ## [Functions](Phoenix.Naming.html#functions) [camelize(value)](Phoenix.Naming.html#camelize/1) Converts a string to camel case. [camelize(value, atom)](Phoenix.Naming.html#camelize/2) [humanize(atom)](Phoenix.Naming.html#humanize/1) Converts an attribute/form field into its humanize version. [resource\_name(alias, suffix \\\\ "")](Phoenix.Naming.html#resource_name/2) Extracts the resource name from an alias. [underscore(value)](Phoenix.Naming.html#underscore/1) Converts a string to underscore case. [unsuffix(value, suffix)](Phoenix.Naming.html#unsuffix/2) Removes the given suffix from the name if it exists. # [](Phoenix.Naming.html#functions)Functions [](Phoenix.Naming.html#camelize/1) # camelize(value) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/naming.ex#L94) ``` @spec camelize(String.t()) :: String.t() ``` Converts a string to camel case. Takes an optional `:lower` flag to return lowerCamelCase. ## [](Phoenix.Naming.html#camelize/1-examples)Examples ``` iex> Phoenix.Naming.camelize("my_app") "MyApp" iex> Phoenix.Naming.camelize("my_app", :lower) "myApp" ``` In general, `camelize` can be thought of as the reverse of `underscore`, however, in some cases formatting may be lost: ``` Phoenix.Naming.underscore "SAPExample" #=> "sap_example" Phoenix.Naming.camelize "sap_example" #=> "SapExample" ``` [](Phoenix.Naming.html#camelize/2) # camelize(value, atom) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/naming.ex#L97) ``` @spec camelize(String.t(), :lower) :: String.t() ``` [](Phoenix.Naming.html#humanize/1) # humanize(atom) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/naming.ex#L120) ``` @spec humanize(atom() | String.t()) :: String.t() ``` Converts an attribute/form field into its humanize version. ## [](Phoenix.Naming.html#humanize/1-examples)Examples ``` iex> Phoenix.Naming.humanize(:username) "Username" iex> Phoenix.Naming.humanize(:created_at) "Created at" iex> Phoenix.Naming.humanize("user_id") "User" ``` [](Phoenix.Naming.html#resource_name/2) # resource\_name(alias, suffix \\\\ "") [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/naming.ex#L19) ``` @spec resource_name(String.Chars.t(), String.t()) :: String.t() ``` Extracts the resource name from an alias. ## [](Phoenix.Naming.html#resource_name/2-examples)Examples ``` iex> Phoenix.Naming.resource_name(MyApp.User) "user" iex> Phoenix.Naming.resource_name(MyApp.UserView, "View") "user" ``` [](Phoenix.Naming.html#underscore/1) # underscore(value) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/naming.ex#L68) ``` @spec underscore(String.t()) :: String.t() ``` Converts a string to underscore case. ## [](Phoenix.Naming.html#underscore/1-examples)Examples ``` iex> Phoenix.Naming.underscore("MyApp") "my_app" ``` In general, `underscore` can be thought of as the reverse of `camelize`, however, in some cases formatting may be lost: ``` Phoenix.Naming.underscore "SAPExample" #=> "sap_example" Phoenix.Naming.camelize "sap_example" #=> "SapExample" ``` [](Phoenix.Naming.html#unsuffix/2) # unsuffix(value, suffix) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/naming.ex#L41) ``` @spec unsuffix(String.t(), String.t()) :: String.t() ``` Removes the given suffix from the name if it exists. ## [](Phoenix.Naming.html#unsuffix/2-examples)Examples ``` iex> Phoenix.Naming.unsuffix("MyApp.User", "View") "MyApp.User" iex> Phoenix.Naming.unsuffix("MyApp.UserView", "View") "MyApp.User" ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.NotAcceptableError.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/exceptions.ex#L1 "View Source") Phoenix.NotAcceptableError exception (Phoenix v1.7.18) Raised when one of the `accept*` headers is not accepted by the server. This exception is commonly raised by [`Phoenix.Controller.accepts/2`](Phoenix.Controller.html#accepts/2) which negotiates the media types the server is able to serve with the contents the client is able to render. If you are seeing this error, you should check if you are listing the desired formats in your `:accepts` plug or if you are setting the proper accept header in the client. The exception contains the acceptable mime types in the `accepts` field. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Param.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/param.ex#L1 "View Source") Phoenix.Param protocol (Phoenix v1.7.18) A protocol that converts data structures into URL parameters. This protocol is used by URL helpers and other parts of the Phoenix stack. For example, when you write: ``` user_path(conn, :edit, @user) ``` Phoenix knows how to extract the `:id` from `@user` thanks to this protocol. By default, Phoenix implements this protocol for integers, binaries, atoms, and structs. For structs, a key `:id` is assumed, but you may provide a specific implementation. Nil values cannot be converted to param. ## [](Phoenix.Param.html#module-custom-parameters)Custom parameters In order to customize the parameter for any struct, one can simply implement this protocol. However, for convenience, this protocol can also be derivable. For example: ``` defmodule User do @derive Phoenix.Param defstruct [:id, :username] end ``` By default, the derived implementation will also use the `:id` key. In case the user does not contain an `:id` key, the key can be specified with an option: ``` defmodule User do @derive {Phoenix.Param, key: :username} defstruct [:username] end ``` will automatically use `:username` in URLs. When using Ecto, you must call `@derive` before your `schema` call: ``` @derive {Phoenix.Param, key: :username} schema "users" do ``` # [](Phoenix.Param.html#summary)Summary ## [Types](Phoenix.Param.html#types) [t()](Phoenix.Param.html#t:t/0) All the types that implement this protocol. ## [Functions](Phoenix.Param.html#functions) [to\_param(term)](Phoenix.Param.html#to_param/1) # [](Phoenix.Param.html#types)Types [](Phoenix.Param.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/param.ex#L1) ``` @type t() :: term() ``` All the types that implement this protocol. # [](Phoenix.Param.html#functions)Functions [](Phoenix.Param.html#to_param/1) # to\_param(term) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/param.ex#L54) ``` @spec to_param(term()) :: String.t() ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Presence.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L1 "View Source") Phoenix.Presence behaviour (Phoenix v1.7.18) Provides Presence tracking to processes and channels. This behaviour provides presence features such as fetching presences for a given topic, as well as handling diffs of join and leave events as they occur in real-time. Using this module defines a supervisor and a module that implements the [`Phoenix.Tracker`](../phoenix_pubsub/2.1.3/Phoenix.Tracker.html) behaviour that uses [`Phoenix.PubSub`](../phoenix_pubsub/2.1.3/Phoenix.PubSub.html) to broadcast presence updates. In case you want to use only a subset of the functionality provided by [`Phoenix.Presence`](Phoenix.Presence.html), such as tracking processes but without broadcasting updates, we recommend that you look at the [`Phoenix.Tracker`](../phoenix_pubsub/2.1.3/Phoenix.Tracker.html) functionality from the `phoenix_pubsub` project. ## [](Phoenix.Presence.html#module-example-usage)Example Usage Start by defining a presence module within your application which uses [`Phoenix.Presence`](Phoenix.Presence.html) and provide the `:otp_app` which holds your configuration, as well as the `:pubsub_server`. ``` defmodule MyAppWeb.Presence do use Phoenix.Presence, otp_app: :my_app, pubsub_server: MyApp.PubSub end ``` The `:pubsub_server` must point to an existing pubsub server running in your application, which is included by default as `MyApp.PubSub` for new applications. Next, add the new supervisor to your supervision tree in `lib/my_app/application.ex`. It must be after the PubSub child and before the endpoint: ``` children = [ ... {Phoenix.PubSub, name: MyApp.PubSub}, MyAppWeb.Presence, MyAppWeb.Endpoint ] ``` Once added, presences can be tracked in your channel after joining: ``` defmodule MyAppWeb.MyChannel do use MyAppWeb, :channel alias MyAppWeb.Presence def join("some:topic", _params, socket) do send(self(), :after_join) {:ok, assign(socket, :user_id, ...)} end def handle_info(:after_join, socket) do {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{ online_at: inspect(System.system_time(:second)) }) push(socket, "presence_state", Presence.list(socket)) {:noreply, socket} end end ``` In the example above, `Presence.track` is used to register this channel's process as a presence for the socket's user ID, with a map of metadata. Next, the current presence information for the socket's topic is pushed to the client as a `"presence_state"` event. Finally, a diff of presence join and leave events will be sent to the client as they happen in real-time with the "presence\_diff" event. The diff structure will be a map of `:joins` and `:leaves` of the form: ``` %{ joins: %{"123" => %{metas: [%{status: "away", phx_ref: ...}]}}, leaves: %{"456" => %{metas: [%{status: "online", phx_ref: ...}]}} }, ``` See [`list/1`](Phoenix.Presence.html#c:list/1) for more information on the presence data structure. ## [](Phoenix.Presence.html#module-fetching-presence-information)Fetching Presence Information Presence metadata should be minimized and used to store small, ephemeral state, such as a user's "online" or "away" status. More detailed information, such as user details that need to be fetched from the database, can be achieved by overriding the [`fetch/2`](Phoenix.Presence.html#c:fetch/2) function. The [`fetch/2`](Phoenix.Presence.html#c:fetch/2) callback is triggered when using [`list/1`](Phoenix.Presence.html#c:list/1) and on every update, and it serves as a mechanism to fetch presence information a single time, before broadcasting the information to all channel subscribers. This prevents N query problems and gives you a single place to group isolated data fetching to extend presence metadata. The function must return a map of data matching the outlined Presence data structure, including the `:metas` key, but can extend the map of information to include any additional information. For example: ``` def fetch(_topic, presences) do users = presences |> Map.keys() |> Accounts.get_users_map() for {key, %{metas: metas}} <- presences, into: %{} do {key, %{metas: metas, user: users[String.to_integer(key)]}} end end ``` Where `Account.get_users_map/1` could be implemented like: ``` def get_users_map(ids) do query = from u in User, where: u.id in ^ids, select: {u.id, u} query |> Repo.all() |> Enum.into(%{}) end ``` The `fetch/2` function above fetches all users from the database who have registered presences for the given topic. The presences information is then extended with a `:user` key of the user's information, while maintaining the required `:metas` field from the original presence data. ## [](Phoenix.Presence.html#module-using-elixir-as-a-presence-client)Using Elixir as a Presence Client Presence is great for external clients, such as JavaScript applications, but it can also be used from an Elixir client process to keep track of presence changes as they happen on the server. This can be accomplished by implementing the optional [`init/1`](Phoenix.Presence.html#c:init/1) and [`handle_metas/4`](Phoenix.Presence.html#c:handle_metas/4) callbacks on your presence module. For example, the following callback receives presence metadata changes, and broadcasts to other Elixir processes about users joining and leaving: ``` defmodule MyApp.Presence do use Phoenix.Presence, otp_app: :my_app, pubsub_server: MyApp.PubSub def init(_opts) do {:ok, %{}} # user-land state end def handle_metas(topic, %{joins: joins, leaves: leaves}, presences, state) do # fetch existing presence information for the joined users and broadcast the # event to all subscribers for {user_id, presence} <- joins do user_data = %{user: presence.user, metas: Map.fetch!(presences, user_id)} msg = {MyApp.PresenceClient, {:join, user_data}} Phoenix.PubSub.local_broadcast(MyApp.PubSub, topic, msg) end # fetch existing presence information for the left users and broadcast the # event to all subscribers for {user_id, presence} <- leaves do metas = case Map.fetch(presences, user_id) do {:ok, presence_metas} -> presence_metas :error -> [] end user_data = %{user: presence.user, metas: metas} msg = {MyApp.PresenceClient, {:leave, user_data}} Phoenix.PubSub.local_broadcast(MyApp.PubSub, topic, msg) end {:ok, state} end end ``` The `handle_metas/4` callback receives the topic, presence diff, current presences for the topic with their metadata, and any user-land state accumulated from init and subsequent `handle_metas/4` calls. In our example implementation, we walk the `:joins` and `:leaves` in the diff, and populate a complete presence from our known presence information. Then we broadcast to the local node subscribers about user joins and leaves. ## [](Phoenix.Presence.html#module-testing-with-presence)Testing with Presence Every time the `fetch` callback is invoked, it is done from a separate process. Given those processes run asynchronously, it is often necessary to guarantee they have been shutdown at the end of every test. This can be done by using ExUnit's `on_exit` hook plus `fetchers_pids` function: ``` on_exit(fn -> for pid <- MyAppWeb.Presence.fetchers_pids() do ref = Process.monitor(pid) assert_receive {:DOWN, ^ref, _, _, _}, 1000 end end) ``` # [](Phoenix.Presence.html#summary)Summary ## [Types](Phoenix.Presence.html#types) [presence()](Phoenix.Presence.html#t:presence/0) [presences()](Phoenix.Presence.html#t:presences/0) [topic()](Phoenix.Presence.html#t:topic/0) ## [Callbacks](Phoenix.Presence.html#callbacks) [fetch(topic, presences)](Phoenix.Presence.html#c:fetch/2) Extend presence information with additional data. [get\_by\_key(arg1, key)](Phoenix.Presence.html#c:get_by_key/2) Returns the map of presence metadata for a socket/topic-key pair. [handle\_metas(topic, diff, presences, state)](Phoenix.Presence.html#c:handle_metas/4) Receives presence metadata changes. [init(state)](Phoenix.Presence.html#c:init/1) Initializes the presence client state. [list(socket\_or\_topic)](Phoenix.Presence.html#c:list/1) Returns presences for a socket/topic. [track(socket, key, meta)](Phoenix.Presence.html#c:track/3) Track a channel's process as a presence. [track(pid, topic, key, meta)](Phoenix.Presence.html#c:track/4) Track an arbitrary process as a presence. [untrack(socket, key)](Phoenix.Presence.html#c:untrack/2) Stop tracking a channel's process. [untrack(pid, topic, key)](Phoenix.Presence.html#c:untrack/3) Stop tracking a process. [update(socket, key, meta)](Phoenix.Presence.html#c:update/3) Update a channel presence's metadata. [update(pid, topic, key, meta)](Phoenix.Presence.html#c:update/4) Update a process presence's metadata. # [](Phoenix.Presence.html#types)Types [](Phoenix.Presence.html#t:presence/0) # presence() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L193) ``` @type presence() :: %{key: String.t(), meta: map()} ``` [](Phoenix.Presence.html#t:presences/0) # presences() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L192) ``` @type presences() :: %{required(String.t()) => %{metas: [map()]}} ``` [](Phoenix.Presence.html#t:topic/0) # topic() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L194) ``` @type topic() :: String.t() ``` # [](Phoenix.Presence.html#callbacks)Callbacks [](Phoenix.Presence.html#c:fetch/2) # fetch(topic, presences) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L334) ``` @callback fetch(topic(), presences()) :: presences() ``` Extend presence information with additional data. When [`list/1`](Phoenix.Presence.html#c:list/1) is used to list all presences of the given `topic`, this callback is triggered once to modify the result before it is broadcasted to all channel subscribers. This avoids N query problems and provides a single place to extend presence metadata. You must return a map of data matching the original result, including the `:metas` key, but can extend the map to include any additional information. The default implementation simply passes `presences` through unchanged. ## [](Phoenix.Presence.html#c:fetch/2-example)Example ``` def fetch(_topic, presences) do query = from u in User, where: u.id in ^Map.keys(presences), select: {u.id, u} users = query |> Repo.all() |> Enum.into(%{}) for {key, %{metas: metas}} <- presences, into: %{} do {key, %{metas: metas, user: users[key]}} end end ``` [](Phoenix.Presence.html#c:get_by_key/2) # get\_by\_key(arg1, key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L305) ``` @callback get_by_key(Phoenix.Socket.t() | topic(), key :: String.t()) :: [presence()] ``` Returns the map of presence metadata for a socket/topic-key pair. ## [](Phoenix.Presence.html#c:get_by_key/2-examples)Examples Uses the same data format as each presence in [`list/1`](Phoenix.Presence.html#c:list/1), but only returns metadata for the presences under a topic and key pair. For example, a user with key `"user1"`, connected to the same chat room `"room:1"` from two devices, could return: ``` iex> MyPresence.get_by_key("room:1", "user1") [%{name: "User 1", metas: [%{device: "Desktop"}, %{device: "Mobile"}]}] ``` Like [`list/1`](Phoenix.Presence.html#c:list/1), the presence metadata is passed to the `fetch` callback of your presence module to fetch any additional information. [](Phoenix.Presence.html#c:handle_metas/4) # handle\_metas(topic, diff, presences, state) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L347)(optional) ``` @callback handle_metas( topic :: String.t(), diff :: map(), presences :: map(), state :: term() ) :: {:ok, term()} ``` Receives presence metadata changes. [](Phoenix.Presence.html#c:init/1) # init(state) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L342)(optional) ``` @callback init(state :: term()) :: {:ok, new_state :: term()} ``` Initializes the presence client state. Invoked when your presence module starts, allows dynamically providing initial state for handling presence metadata. [](Phoenix.Presence.html#c:list/1) # list(socket\_or\_topic) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L287) ``` @callback list(socket_or_topic :: Phoenix.Socket.t() | topic()) :: presences() ``` Returns presences for a socket/topic. ## [](Phoenix.Presence.html#c:list/1-presence-data-structure)Presence data structure The presence information is returned as a map with presences grouped by key, cast as a string, and accumulated metadata, with the following form: ``` %{key => %{metas: [%{phx_ref: ..., ...}, ...]}} ``` For example, imagine a user with id `123` online from two different devices, as well as a user with id `456` online from just one device. The following presence information might be returned: ``` %{"123" => %{metas: [%{status: "away", phx_ref: ...}, %{status: "online", phx_ref: ...}]}, "456" => %{metas: [%{status: "online", phx_ref: ...}]}} ``` The keys of the map will usually point to a resource ID. The value will contain a map with a `:metas` key containing a list of metadata for each resource. Additionally, every metadata entry will contain a `:phx_ref` key which can be used to uniquely identify metadata for a given key. In the event that the metadata was previously updated, a `:phx_ref_prev` key will be present containing the previous `:phx_ref` value. [](Phoenix.Presence.html#c:track/3) # track(socket, key, meta) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L215) ``` @callback track(socket :: Phoenix.Socket.t(), key :: String.t(), meta :: map()) :: {:ok, ref :: binary()} | {:error, reason :: term()} ``` Track a channel's process as a presence. Tracked presences are grouped by `key`, cast as a string. For example, to group each user's channels together, use user IDs as keys. Each presence can be associated with a map of metadata to store small, ephemeral state, such as a user's online status. To store detailed information, see [`fetch/2`](Phoenix.Presence.html#c:fetch/2). ## [](Phoenix.Presence.html#c:track/3-example)Example ``` alias MyApp.Presence def handle_info(:after_join, socket) do {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{ online_at: inspect(System.system_time(:second)) }) {:noreply, socket} end ``` [](Phoenix.Presence.html#c:track/4) # track(pid, topic, key, meta) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L224) ``` @callback track(pid(), topic(), key :: String.t(), meta :: map()) :: {:ok, ref :: binary()} | {:error, reason :: term()} ``` Track an arbitrary process as a presence. Same with `track/3`, except track any process by `topic` and `key`. [](Phoenix.Presence.html#c:untrack/2) # untrack(socket, key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L231) ``` @callback untrack(socket :: Phoenix.Socket.t(), key :: String.t()) :: :ok ``` Stop tracking a channel's process. [](Phoenix.Presence.html#c:untrack/3) # untrack(pid, topic, key) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L236) ``` @callback untrack(pid(), topic(), key :: String.t()) :: :ok ``` Stop tracking a process. [](Phoenix.Presence.html#c:update/3) # update(socket, key, meta) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L244) ``` @callback update( socket :: Phoenix.Socket.t(), key :: String.t(), meta :: map() | (map() -> map()) ) :: {:ok, ref :: binary()} | {:error, reason :: term()} ``` Update a channel presence's metadata. Replace a presence's metadata by passing a new map or a function that takes the current map and returns a new one. [](Phoenix.Presence.html#c:update/4) # update(pid, topic, key, meta) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/presence.ex#L257) ``` @callback update(pid(), topic(), key :: String.t(), meta :: map() | (map() -> map())) :: {:ok, ref :: binary()} | {:error, reason :: term()} ``` Update a process presence's metadata. Same as `update/3`, but with an arbitrary process. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Router.MalformedURIError.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L21 "View Source") Phoenix.Router.MalformedURIError exception (Phoenix v1.7.18) Exception raised when the URI is malformed on matching. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Router.NoRouteError.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L2 "View Source") Phoenix.Router.NoRouteError exception (Phoenix v1.7.18) Exception raised when no route is found. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Router.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1 "View Source") Phoenix.Router (Phoenix v1.7.18) Defines a Phoenix router. The router provides a set of macros for generating routes that dispatch to specific controllers and actions. Those macros are named after HTTP verbs. For example: ``` defmodule MyAppWeb.Router do use Phoenix.Router get "/pages/:page", PageController, :show end ``` The [`get/3`](Phoenix.Router.html#get/3) macro above accepts a request to `/pages/hello` and dispatches it to `PageController`'s `show` action with `%{"page" => "hello"}` in `params`. Phoenix's router is extremely efficient, as it relies on Elixir pattern matching for matching routes and serving requests. ## [](Phoenix.Router.html#module-routing)Routing [`get/3`](Phoenix.Router.html#get/3), [`post/3`](Phoenix.Router.html#post/3), [`put/3`](Phoenix.Router.html#put/3), and other macros named after HTTP verbs are used to create routes. The route: ``` get "/pages", PageController, :index ``` matches a `GET` request to `/pages` and dispatches it to the `index` action in `PageController`. ``` get "/pages/:page", PageController, :show ``` matches `/pages/hello` and dispatches to the `show` action with `%{"page" => "hello"}` in `params`. ``` defmodule PageController do def show(conn, params) do # %{"page" => "hello"} == params end end ``` Partial and multiple segments can be matched. For example: ``` get "/api/v:version/pages/:id", PageController, :show ``` matches `/api/v1/pages/2` and puts `%{"version" => "1", "id" => "2"}` in `params`. Only the trailing part of a segment can be captured. Routes are matched from top to bottom. The second route here: ``` get "/pages/:page", PageController, :show get "/pages/hello", PageController, :hello ``` will never match `/pages/hello` because `/pages/:page` matches that first. Routes can use glob-like patterns to match trailing segments. ``` get "/pages/*page", PageController, :show ``` matches `/pages/hello/world` and puts the globbed segments in `params["page"]`. ``` GET /pages/hello/world %{"page" => ["hello", "world"]} = params ``` Globs cannot have prefixes nor suffixes, but can be mixed with variables: ``` get "/pages/he:page/*rest", PageController, :show ``` matches ``` GET /pages/hello %{"page" => "llo", "rest" => []} = params GET /pages/hey/there/world %{"page" => "y", "rest" => ["there" "world"]} = params ``` #### Why the macros? Phoenix does its best to keep the usage of macros low. You may have noticed, however, that the [`Phoenix.Router`](Phoenix.Router.html) relies heavily on macros. Why is that? We use `get`, `post`, `put`, and `delete` to define your routes. We use macros for two purposes: - They define the routing engine, used on every request, to choose which controller to dispatch the request to. Thanks to macros, Phoenix compiles all of your routes to a single case-statement with pattern matching rules, which is heavily optimized by the Erlang VM - For each route you define, we also define metadata to implement [`Phoenix.VerifiedRoutes`](Phoenix.VerifiedRoutes.html). As we will soon learn, verified routes allows to us to reference any route as if it is a plain looking string, except it is verified by the compiler to be valid (making it much harder to ship broken links, forms, mails, etc to production) In other words, the router relies on macros to build applications that are faster and safer. Also remember that macros in Elixir are compile-time only, which gives plenty of stability after the code is compiled. Phoenix also provides introspection for all defined routes via [`mix phx.routes`](Mix.Tasks.Phx.Routes.html). ## [](Phoenix.Router.html#module-generating-routes)Generating routes For generating routes inside your application, see the [`Phoenix.VerifiedRoutes`](Phoenix.VerifiedRoutes.html) documentation for `~p` based route generation which is the preferred way to generate route paths and URLs with compile-time verification. Phoenix also supports generating function helpers, which was the default mechanism in Phoenix v1.6 and earlier. We will explore it next. ### [](Phoenix.Router.html#module-helpers-deprecated)Helpers (deprecated) Phoenix generates a module `Helpers` inside your router by default, which contains named helpers to help developers generate and keep their routes up to date. Helpers can be disabled by passing `helpers: false` to `use Phoenix.Router`. Helpers are automatically generated based on the controller name. For example, the route: ``` get "/pages/:page", PageController, :show ``` will generate the following named helper: ``` MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, "hello") "/pages/hello" MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, "hello", some: "query") "/pages/hello?some=query" MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, "hello") "http://example.com/pages/hello" MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, "hello", some: "query") "http://example.com/pages/hello?some=query" ``` If the route contains glob-like patterns, parameters for those have to be given as list: ``` MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, ["hello", "world"]) "/pages/hello/world" ``` The URL generated in the named URL helpers is based on the configuration for `:url`, `:http` and `:https`. However, if for some reason you need to manually control the URL generation, the url helpers also allow you to pass in a [`URI`](../elixir/URI.html) struct: ``` uri = %URI{scheme: "https", host: "other.example.com"} MyAppWeb.Router.Helpers.page_url(uri, :show, "hello") "https://other.example.com/pages/hello" ``` The named helper can also be customized with the `:as` option. Given the route: ``` get "/pages/:page", PageController, :show, as: :special_page ``` the named helper will be: ``` MyAppWeb.Router.Helpers.special_page_path(conn, :show, "hello") "/pages/hello" ``` ## [](Phoenix.Router.html#module-scopes-and-resources)Scopes and Resources It is very common in Phoenix applications to namespace all of your routes under the application scope: ``` scope "/", MyAppWeb do get "/pages/:id", PageController, :show end ``` The route above will dispatch to `MyAppWeb.PageController`. This syntax is not only convenient for developers, since we don't have to repeat the `MyAppWeb.` prefix on all routes, but it also allows Phoenix to put less pressure on the Elixir compiler. If instead we had written: ``` get "/pages/:id", MyAppWeb.PageController, :show ``` The Elixir compiler would infer that the router depends directly on `MyAppWeb.PageController`, which is not true. By using scopes, Phoenix can properly hint to the Elixir compiler the controller is not an actual dependency of the router. This provides more efficient compilation times. Scopes allow us to scope on any path or even on the helper name: ``` scope "/v1", MyAppWeb, host: "api." do get "/pages/:id", PageController, :show end ``` For example, the route above will match on the path `"/api/v1/pages/1"` and the named route will be `api_v1_page_path`, as expected from the values given to [`scope/2`](Phoenix.Router.html#scope/2) option. Like all paths you can define dynamic segments that will be applied as parameters in the controller: ``` scope "/api/:version", MyAppWeb do get "/pages/:id", PageController, :show end ``` For example, the route above will match on the path `"/api/v1/pages/1"` and in the controller the `params` argument will have a map with the key `:version` with the value `"v1"`. Phoenix also provides a [`resources/4`](Phoenix.Router.html#resources/4) macro that allows developers to generate "RESTful" routes to a given resource: ``` defmodule MyAppWeb.Router do use Phoenix.Router resources "/pages", PageController, only: [:show] resources "/users", UserController, except: [:delete] end ``` Finally, Phoenix ships with a [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) task that nicely formats all routes in a given router. We can use it to verify all routes included in the router above: ``` $ mix phx.routes page_path GET /pages/:id PageController.show/2 user_path GET /users UserController.index/2 user_path GET /users/:id/edit UserController.edit/2 user_path GET /users/new UserController.new/2 user_path GET /users/:id UserController.show/2 user_path POST /users UserController.create/2 user_path PATCH /users/:id UserController.update/2 PUT /users/:id UserController.update/2 ``` One can also pass a router explicitly as an argument to the task: ``` $ mix phx.routes MyAppWeb.Router ``` Check [`scope/2`](Phoenix.Router.html#scope/2) and [`resources/4`](Phoenix.Router.html#resources/4) for more information. ## [](Phoenix.Router.html#module-pipelines-and-plugs)Pipelines and plugs Once a request arrives at the Phoenix router, it performs a series of transformations through pipelines until the request is dispatched to a desired route. Such transformations are defined via plugs, as defined in the [Plug](https://github.com/elixir-lang/plug) specification. Once a pipeline is defined, it can be piped through per scope. For example: ``` defmodule MyAppWeb.Router do use Phoenix.Router pipeline :browser do plug :fetch_session plug :accepts, ["html"] end scope "/" do pipe_through :browser # browser related routes and resources end end ``` [`Phoenix.Router`](Phoenix.Router.html) imports functions from both [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) and [`Phoenix.Controller`](Phoenix.Controller.html) to help define plugs. In the example above, `fetch_session/2` comes from [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) while `accepts/2` comes from [`Phoenix.Controller`](Phoenix.Controller.html). Note that router pipelines are only invoked after a route is found. No plug is invoked in case no matches were found. ## [](Phoenix.Router.html#module-how-to-organize-my-routes)How to organize my routes? In Phoenix, we tend to define several pipelines, that provide specific functionality. For example, the `pipeline :browser` above includes plugs that are common for all routes that are meant to be accessed by a browser. Similarly, if you are also serving `:api` requests, you would have a separate `:api` pipeline that validates information specific to your endpoints. Perhaps more importantly, it is also very common to define pipelines specific to authentication and authorization. For example, you might have a pipeline that requires all users are authenticated. Another pipeline may enforce only admin users can access certain routes. Since routes are matched top to bottom, it is recommended to place the authenticated/authorized routes before the less restricted routes to ensure they are matched first. Once your pipelines are defined, you reuse the pipelines in the desired scopes, grouping your routes around their pipelines. For example, imagine you are building a blog. Anyone can read a post, but only authenticated users can create them. Your routes could look like this: ``` pipeline :browser do plug :fetch_session plug :accepts, ["html"] end pipeline :auth do plug :ensure_authenticated end scope "/" do pipe_through [:browser, :auth] get "/posts/new", PostController, :new post "/posts", PostController, :create end scope "/" do pipe_through [:browser] get "/posts", PostController, :index get "/posts/:id", PostController, :show end ``` Note in the above how the routes are split across different scopes. While the separation can be confusing at first, it has one big upside: it is very easy to inspect your routes and see all routes that, for example, require authentication and which ones do not. This helps with auditing and making sure your routes have the proper scope. You can create as few or as many scopes as you want. Because pipelines are reusable across scopes, they help encapsulate common functionality and you can compose them as necessary on each scope you define. # [](Phoenix.Router.html#summary)Summary ## [Reflection](Phoenix.Router.html#reflection) [route\_info(router, method, path, host)](Phoenix.Router.html#route_info/4) Returns the compile-time route info and runtime path params for a request. [scoped\_alias(router\_module, alias)](Phoenix.Router.html#scoped_alias/2) Returns the full alias with the current scope's aliased prefix. [scoped\_path(router\_module, path)](Phoenix.Router.html#scoped_path/2) Returns the full path with the current scope's path prefix. ## [Functions](Phoenix.Router.html#functions) [connect(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#connect/4) Generates a route to handle a connect request to the given path. [delete(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#delete/4) Generates a route to handle a delete request to the given path. [forward(path, plug, plug\_opts \\\\ \[\], router\_opts \\\\ \[\])](Phoenix.Router.html#forward/4) Forwards a request at the given path to a plug. [get(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#get/4) Generates a route to handle a get request to the given path. [head(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#head/4) Generates a route to handle a head request to the given path. [match(verb, path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#match/5) Generates a route match based on an arbitrary HTTP method. [options(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#options/4) Generates a route to handle a options request to the given path. [patch(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#patch/4) Generates a route to handle a patch request to the given path. [pipe\_through(pipes)](Phoenix.Router.html#pipe_through/1) Defines a list of plugs (and pipelines) to send the connection through. [pipeline(plug, list)](Phoenix.Router.html#pipeline/2) Defines a plug pipeline. [plug(plug, opts \\\\ \[\])](Phoenix.Router.html#plug/2) Defines a plug inside a pipeline. [post(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#post/4) Generates a route to handle a post request to the given path. [put(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#put/4) Generates a route to handle a put request to the given path. [resources(path, controller)](Phoenix.Router.html#resources/2) See [`resources/4`](Phoenix.Router.html#resources/4). [resources(path, controller, opts)](Phoenix.Router.html#resources/3) See [`resources/4`](Phoenix.Router.html#resources/4). [resources(path, controller, opts, list)](Phoenix.Router.html#resources/4) Defines "RESTful" routes for a resource. [routes(router)](Phoenix.Router.html#routes/1) Returns all routes information from the given router. [scope(options, list)](Phoenix.Router.html#scope/2) Defines a scope in which routes can be nested. [scope(path, options, list)](Phoenix.Router.html#scope/3) Define a scope with the given path. [scope(path, alias, options, list)](Phoenix.Router.html#scope/4) Defines a scope with the given path and alias. [trace(path, plug, plug\_opts, options \\\\ \[\])](Phoenix.Router.html#trace/4) Generates a route to handle a trace request to the given path. # [](Phoenix.Router.html#reflection)Reflection [](Phoenix.Router.html#route_info/4) # route\_info(router, method, path, host) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1271) Returns the compile-time route info and runtime path params for a request. The `path` can be either a string or the `path_info` segments. A map of metadata is returned with the following keys: - `:log` - the configured log level. For example `:debug` - `:path_params` - the map of runtime path params - `:pipe_through` - the list of pipelines for the route's scope, for example `[:browser]` - `:plug` - the plug to dispatch the route to, for example `AppWeb.PostController` - `:plug_opts` - the options to pass when calling the plug, for example: `:index` - `:route` - the string route pattern, such as `"/posts/:id"` ## [](Phoenix.Router.html#route_info/4-examples)Examples ``` iex> Phoenix.Router.route_info(AppWeb.Router, "GET", "/posts/123", "myhost") %{ log: :debug, path_params: %{"id" => "123"}, pipe_through: [:browser], plug: AppWeb.PostController, plug_opts: :show, route: "/posts/:id", } iex> Phoenix.Router.route_info(MyRouter, "GET", "/not-exists", "myhost") :error ``` [](Phoenix.Router.html#scoped_alias/2) # scoped\_alias(router\_module, alias) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1190) Returns the full alias with the current scope's aliased prefix. Useful for applying the same short-hand alias handling to other values besides the second argument in route definitions. ## [](Phoenix.Router.html#scoped_alias/2-examples)Examples ``` scope "/", MyPrefix do get "/", ProxyPlug, controller: scoped_alias(__MODULE__, MyController) end ``` [](Phoenix.Router.html#scoped_path/2) # scoped\_path(router\_module, path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1198) Returns the full path with the current scope's path prefix. # [](Phoenix.Router.html#functions)Functions [](Phoenix.Router.html#connect/4) # connect(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a connect request to the given path. ``` connect("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#delete/4) # delete(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a delete request to the given path. ``` delete("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#forward/4) # forward(path, plug, plug\_opts \\\\ \[], router\_opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1225)(macro) Forwards a request at the given path to a plug. All paths that match the forwarded prefix will be sent to the forwarded plug. This is useful for sharing a router between applications or even breaking a big router into smaller ones. The router pipelines will be invoked prior to forwarding the connection. However, we don't advise forwarding to another endpoint. The reason is that plugs defined by your app and the forwarded endpoint would be invoked twice, which may lead to errors. ## [](Phoenix.Router.html#forward/4-examples)Examples ``` scope "/", MyApp do pipe_through [:browser, :admin] forward "/admin", SomeLib.AdminDashboard forward "/api", ApiRouter end ``` [](Phoenix.Router.html#get/4) # get(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a get request to the given path. ``` get("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#head/4) # head(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a head request to the given path. ``` head("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#match/5) # match(verb, path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L768)(macro) Generates a route match based on an arbitrary HTTP method. Useful for defining routes not included in the built-in macros. The catch-all verb, `:*`, may also be used to match all HTTP methods. ## [](Phoenix.Router.html#match/5-options)Options - `:as` - configures the named helper. If `nil`, does not generate a helper. Has no effect when using verified routes exclusively - `:alias` - configure if the scope alias should be applied to the route. Defaults to true, disables scoping if false. - `:log` - the level to log the route dispatching under, may be set to false. Defaults to `:debug`. Route dispatching contains information about how the route is handled (which controller action is called, what parameters are available and which pipelines are used) and is separate from the plug level logging. To alter the plug log level, please see [https://hexdocs.pm/phoenix/Phoenix.Logger.html#module-dynamic-log-level](Phoenix.Logger.html#module-dynamic-log-level). - `:private` - a map of private data to merge into the connection when a route matches - `:assigns` - a map of data to merge into the connection when a route matches - `:metadata` - a map of metadata used by the telemetry events and returned by [`route_info/4`](Phoenix.Router.html#route_info/4) - `:warn_on_verify` - the boolean for whether matches to this route trigger an unmatched route warning for [`Phoenix.VerifiedRoutes`](Phoenix.VerifiedRoutes.html). It is useful to ignore an otherwise catch-all route definition from being matched when verifying routes. Defaults `false`. ## [](Phoenix.Router.html#match/5-examples)Examples ``` match(:move, "/events/:id", EventController, :move) match(:*, "/any", SomeController, :any) ``` [](Phoenix.Router.html#options/4) # options(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a options request to the given path. ``` options("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#patch/4) # patch(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a patch request to the given path. ``` patch("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#pipe_through/1) # pipe\_through(pipes) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L923)(macro) Defines a list of plugs (and pipelines) to send the connection through. Plugs are specified using the atom name of any imported 2-arity function which takes a `%Plug.Conn{}` and options and returns a `%Plug.Conn{}`; for example, `:require_authenticated_user`. Pipelines are defined in the router; see [`pipeline/2`](Phoenix.Router.html#pipeline/2) for more information. ``` pipe_through [:my_imported_function, :my_pipeline] ``` [](Phoenix.Router.html#pipeline/2) # pipeline(plug, list) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L822)(macro) Defines a plug pipeline. Pipelines are defined at the router root and can be used from any scope. ## [](Phoenix.Router.html#pipeline/2-examples)Examples ``` pipeline :api do plug :token_authentication plug :dispatch end ``` A scope may then use this pipeline as: ``` scope "/" do pipe_through :api end ``` Every time [`pipe_through/1`](Phoenix.Router.html#pipe_through/1) is called, the new pipelines are appended to the ones previously given. [](Phoenix.Router.html#plug/2) # plug(plug, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L875)(macro) Defines a plug inside a pipeline. See [`pipeline/2`](Phoenix.Router.html#pipeline/2) for more information. [](Phoenix.Router.html#post/4) # post(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a post request to the given path. ``` post("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#put/4) # put(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a put request to the given path. ``` put("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [](Phoenix.Router.html#resources/2) # resources(path, controller) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1035)(macro) See [`resources/4`](Phoenix.Router.html#resources/4). [](Phoenix.Router.html#resources/3) # resources(path, controller, opts) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1024)(macro) See [`resources/4`](Phoenix.Router.html#resources/4). [](Phoenix.Router.html#resources/4) # resources(path, controller, opts, list) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1017)(macro) Defines "RESTful" routes for a resource. The given definition: ``` resources "/users", UserController ``` will include routes to the following actions: - `GET /users` =&gt; `:index` - `GET /users/new` =&gt; `:new` - `POST /users` =&gt; `:create` - `GET /users/:id` =&gt; `:show` - `GET /users/:id/edit` =&gt; `:edit` - `PATCH /users/:id` =&gt; `:update` - `PUT /users/:id` =&gt; `:update` - `DELETE /users/:id` =&gt; `:delete` ## [](Phoenix.Router.html#resources/4-options)Options This macro accepts a set of options: - `:only` - a list of actions to generate routes for, for example: `[:show, :edit]` - `:except` - a list of actions to exclude generated routes from, for example: `[:delete]` - `:param` - the name of the parameter for this resource, defaults to `"id"` - `:name` - the prefix for this resource. This is used for the named helper and as the prefix for the parameter in nested resources. The default value is automatically derived from the controller name, i.e. `UserController` will have name `"user"` - `:as` - configures the named helper. If `nil`, does not generate a helper. Has no effect when using verified routes exclusively - `:singleton` - defines routes for a singleton resource that is looked up by the client without referencing an ID. Read below for more information ## [](Phoenix.Router.html#resources/4-singleton-resources)Singleton resources When a resource needs to be looked up without referencing an ID, because it contains only a single entry in the given context, the `:singleton` option can be used to generate a set of routes that are specific to such single resource: - `GET /user` =&gt; `:show` - `GET /user/new` =&gt; `:new` - `POST /user` =&gt; `:create` - `GET /user/edit` =&gt; `:edit` - `PATCH /user` =&gt; `:update` - `PUT /user` =&gt; `:update` - `DELETE /user` =&gt; `:delete` Usage example: ``` resources "/account", AccountController, only: [:show], singleton: true ``` ## [](Phoenix.Router.html#resources/4-nested-resources)Nested Resources This macro also supports passing a nested block of route definitions. This is helpful for nesting children resources within their parents to generate nested routes. The given definition: ``` resources "/users", UserController do resources "/posts", PostController end ``` will include the following routes: ``` user_post_path GET /users/:user_id/posts PostController :index user_post_path GET /users/:user_id/posts/:id/edit PostController :edit user_post_path GET /users/:user_id/posts/new PostController :new user_post_path GET /users/:user_id/posts/:id PostController :show user_post_path POST /users/:user_id/posts PostController :create user_post_path PATCH /users/:user_id/posts/:id PostController :update PUT /users/:user_id/posts/:id PostController :update user_post_path DELETE /users/:user_id/posts/:id PostController :delete ``` [](Phoenix.Router.html#routes/1) # routes(router) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1237) Returns all routes information from the given router. [](Phoenix.Router.html#scope/2) # scope(options, list) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1088)(macro) Defines a scope in which routes can be nested. ## [](Phoenix.Router.html#scope/2-examples)Examples ``` scope path: "/api/v1", alias: API.V1 do get "/pages/:id", PageController, :show end ``` The generated route above will match on the path `"/api/v1/pages/:id"` and will dispatch to `:show` action in `API.V1.PageController`. A named helper `api_v1_page_path` will also be generated. ## [](Phoenix.Router.html#scope/2-options)Options The supported options are: - `:path` - a string containing the path scope. - `:as` - a string or atom containing the named helper scope. When set to false, it resets the nested helper scopes. Has no effect when using verified routes exclusively - `:alias` - an alias (atom) containing the controller scope. When set to false, it resets all nested aliases. - `:host` - a string or list of strings containing the host scope, or prefix host scope, ie `"foo.bar.com"`, `"foo."` - `:private` - a map of private data to merge into the connection when a route matches - `:assigns` - a map of data to merge into the connection when a route matches - `:log` - the level to log the route dispatching under, may be set to false. Defaults to `:debug`. Route dispatching contains information about how the route is handled (which controller action is called, what parameters are available and which pipelines are used) and is separate from the plug level logging. To alter the plug log level, please see [https://hexdocs.pm/phoenix/Phoenix.Logger.html#module-dynamic-log-level](Phoenix.Logger.html#module-dynamic-log-level). [](Phoenix.Router.html#scope/3) # scope(path, options, list) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1115)(macro) Define a scope with the given path. This function is a shortcut for: ``` scope path: path do ... end ``` ## [](Phoenix.Router.html#scope/3-examples)Examples ``` scope "/v1", host: "api." do get "/pages/:id", PageController, :show end ``` [](Phoenix.Router.html#scope/4) # scope(path, alias, options, list) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L1152)(macro) Defines a scope with the given path and alias. This function is a shortcut for: ``` scope path: path, alias: alias do ... end ``` ## [](Phoenix.Router.html#scope/4-examples)Examples ``` scope "/v1", API.V1, host: "api." do get "/pages/:id", PageController, :show end ``` [](Phoenix.Router.html#trace/4) # trace(path, plug, plug\_opts, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/router.ex#L780)(macro) Generates a route to handle a trace request to the given path. ``` trace("/events/:id", EventController, :action) ``` See [`match/5`](Phoenix.Router.html#match/5) for options. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Socket.Broadcast.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/message.ex#L58 "View Source") Phoenix.Socket.Broadcast (Phoenix v1.7.18) Defines a message sent from pubsub to channels and vice-versa. The message format requires the following keys: - `:topic` - The string topic or topic:subtopic pair namespace, for example "messages", "messages:123" - `:event`- The string event name, for example "phx\_join" - `:payload` - The message payload # [](Phoenix.Socket.Broadcast.html#summary)Summary ## [Types](Phoenix.Socket.Broadcast.html#types) [t()](Phoenix.Socket.Broadcast.html#t:t/0) # [](Phoenix.Socket.Broadcast.html#types)Types [](Phoenix.Socket.Broadcast.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/message.ex#L70) ``` @type t() :: %Phoenix.Socket.Broadcast{event: term(), payload: term(), topic: term()} ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Socket.InvalidMessageError.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L251 "View Source") Phoenix.Socket.InvalidMessageError exception (Phoenix v1.7.18) Raised when the socket message is invalid. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Socket.Message.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/message.ex#L1 "View Source") Phoenix.Socket.Message (Phoenix v1.7.18) Defines a message dispatched over transport to channels and vice-versa. The message format requires the following keys: - `:topic` - The string topic or topic:subtopic pair namespace, for example "messages", "messages:123" - `:event`- The string event name, for example "phx\_join" - `:payload` - The message payload - `:ref` - The unique string ref - `:join_ref` - The unique string ref when joining # [](Phoenix.Socket.Message.html#summary)Summary ## [Types](Phoenix.Socket.Message.html#types) [t()](Phoenix.Socket.Message.html#t:t/0) ## [Functions](Phoenix.Socket.Message.html#functions) [from\_map!(map)](Phoenix.Socket.Message.html#from_map!/1) Converts a map with string keys into a message struct. # [](Phoenix.Socket.Message.html#types)Types [](Phoenix.Socket.Message.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/message.ex#L16) ``` @type t() :: %Phoenix.Socket.Message{ event: term(), join_ref: term(), payload: term(), ref: term(), topic: term() } ``` # [](Phoenix.Socket.Message.html#functions)Functions [](Phoenix.Socket.Message.html#from_map!/1) # from\_map!(map) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/message.ex#L24) Converts a map with string keys into a message struct. Raises [`Phoenix.Socket.InvalidMessageError`](Phoenix.Socket.InvalidMessageError.html) if not valid. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Socket.Reply.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/message.ex#L40 "View Source") Phoenix.Socket.Reply (Phoenix v1.7.18) Defines a reply sent from channels to transports. The message format requires the following keys: - `:topic` - The string topic or topic:subtopic pair namespace, for example "messages", "messages:123" - `:status` - The reply status as an atom - `:payload` - The reply payload - `:ref` - The unique string ref - `:join_ref` - The unique string ref when joining # [](Phoenix.Socket.Reply.html#summary)Summary ## [Types](Phoenix.Socket.Reply.html#types) [t()](Phoenix.Socket.Reply.html#t:t/0) # [](Phoenix.Socket.Reply.html#types)Types [](Phoenix.Socket.Reply.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/message.ex#L54) ``` @type t() :: %Phoenix.Socket.Reply{ join_ref: term(), payload: term(), ref: term(), status: term(), topic: term() } ``` [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Socket.Serializer.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/serializer.ex#L1 "View Source") Phoenix.Socket.Serializer behaviour (Phoenix v1.7.18) A behaviour that serializes incoming and outgoing socket messages. By default Phoenix provides a serializer that encodes to JSON and decodes JSON messages. Custom serializers may be configured in the socket. # [](Phoenix.Socket.Serializer.html#summary)Summary ## [Callbacks](Phoenix.Socket.Serializer.html#callbacks) [decode!(iodata, options)](Phoenix.Socket.Serializer.html#c:decode!/2) Decodes iodata into [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) struct. [encode!(arg1)](Phoenix.Socket.Serializer.html#c:encode!/1) Encodes [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) and [`Phoenix.Socket.Reply`](Phoenix.Socket.Reply.html) structs to push format. [fastlane!(t)](Phoenix.Socket.Serializer.html#c:fastlane!/1) Encodes a [`Phoenix.Socket.Broadcast`](Phoenix.Socket.Broadcast.html) struct to fastlane format. # [](Phoenix.Socket.Serializer.html#callbacks)Callbacks [](Phoenix.Socket.Serializer.html#c:decode!/2) # decode!(iodata, options) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/serializer.ex#L28) ``` @callback decode!(iodata(), options :: Keyword.t()) :: Phoenix.Socket.Message.t() ``` Decodes iodata into [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) struct. [](Phoenix.Socket.Serializer.html#c:encode!/1) # encode!(arg1) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/serializer.ex#L21) ``` @callback encode!(Phoenix.Socket.Message.t() | Phoenix.Socket.Reply.t()) :: {:socket_push, :text, iodata()} | {:socket_push, :binary, iodata()} ``` Encodes [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) and [`Phoenix.Socket.Reply`](Phoenix.Socket.Reply.html) structs to push format. [](Phoenix.Socket.Serializer.html#c:fastlane!/1) # fastlane!(t) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/serializer.ex#L14) ``` @callback fastlane!(Phoenix.Socket.Broadcast.t()) :: {:socket_push, :text, iodata()} | {:socket_push, :binary, iodata()} ``` Encodes a [`Phoenix.Socket.Broadcast`](Phoenix.Socket.Broadcast.html) struct to fastlane format. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Socket.Transport.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L1 "View Source") Phoenix.Socket.Transport behaviour (Phoenix v1.7.18) Outlines the Socket &lt;-&gt; Transport communication. Each transport, such as websockets and longpolling, must interact with a socket. This module defines said behaviour. [`Phoenix.Socket`](Phoenix.Socket.html) is just one possible implementation of a socket that multiplexes events over multiple channels. If you implement this behaviour, then a transport can directly invoke your implementation, without passing through channels. This module also provides convenience functions for implementing transports. ## [](Phoenix.Socket.Transport.html#module-example)Example Here is a simple echo socket implementation: ``` defmodule EchoSocket do @behaviour Phoenix.Socket.Transport def child_spec(opts) do # We won't spawn any process, so let's ignore the child spec :ignore end def connect(state) do # Callback to retrieve relevant data from the connection. # The map contains options, params, transport and endpoint keys. {:ok, state} end def init(state) do # Now we are effectively inside the process that maintains the socket. {:ok, state} end def handle_in({text, _opts}, state) do {:reply, :ok, {:text, text}, state} end def handle_info(_, state) do {:ok, state} end def terminate(_reason, _state) do :ok end end ``` It can be mounted in your endpoint like any other socket: ``` socket "/socket", EchoSocket, websocket: true, longpoll: true ``` You can now interact with the socket under `/socket/websocket` and `/socket/longpoll`. ## [](Phoenix.Socket.Transport.html#module-custom-transports)Custom transports Sockets are operated by a transport. When a transport is defined, it usually receives a socket module and the module will be invoked when certain events happen at the transport level. Whenever the transport receives a new connection, it should invoke the [`connect/1`](Phoenix.Socket.Transport.html#c:connect/1) callback with a map of metadata. Different sockets may require different metadata. If the connection is accepted, the transport can move the connection to another process, if so desires, or keep using the same process. The process responsible for managing the socket should then call [`init/1`](Phoenix.Socket.Transport.html#c:init/1). For each message received from the client, the transport must call [`handle_in/2`](Phoenix.Socket.Transport.html#c:handle_in/2) on the socket. For each informational message the transport receives, it should call [`handle_info/2`](Phoenix.Socket.Transport.html#c:handle_info/2) on the socket. Transports can optionally implement [`handle_control/2`](Phoenix.Socket.Transport.html#c:handle_control/2) for handling control frames such as `:ping` and `:pong`. On termination, [`terminate/2`](Phoenix.Socket.Transport.html#c:terminate/2) must be called. A special atom with reason `:closed` can be used to specify that the client terminated the connection. ## [](Phoenix.Socket.Transport.html#module-booting)Booting Whenever your endpoint starts, it will automatically invoke the `child_spec/1` on each listed socket and start that specification under the endpoint supervisor. Since the socket supervision tree is started by the endpoint, any custom transport must be started after the endpoint in a supervision tree. # [](Phoenix.Socket.Transport.html#summary)Summary ## [Types](Phoenix.Socket.Transport.html#types) [state()](Phoenix.Socket.Transport.html#t:state/0) ## [Callbacks](Phoenix.Socket.Transport.html#callbacks) [child\_spec(keyword)](Phoenix.Socket.Transport.html#c:child_spec/1) Returns a child specification for socket management. [connect(transport\_info)](Phoenix.Socket.Transport.html#c:connect/1) Connects to the socket. [drainer\_spec(keyword)](Phoenix.Socket.Transport.html#c:drainer_spec/1) Returns a child specification for terminating the socket. [handle\_control({}, state)](Phoenix.Socket.Transport.html#c:handle_control/2) Handles incoming control frames. [handle\_in({}, state)](Phoenix.Socket.Transport.html#c:handle_in/2) Handles incoming socket messages. [handle\_info(message, state)](Phoenix.Socket.Transport.html#c:handle_info/2) Handles info messages. [init(state)](Phoenix.Socket.Transport.html#c:init/1) Initializes the socket state. [terminate(reason, state)](Phoenix.Socket.Transport.html#c:terminate/2) Invoked on termination. ## [Functions](Phoenix.Socket.Transport.html#functions) [check\_origin(conn, handler, endpoint, opts, sender \\\\ &amp;Plug.Conn.send\_resp/1)](Phoenix.Socket.Transport.html#check_origin/5) Checks the origin request header against the list of allowed origins. [check\_subprotocols(conn, subprotocols)](Phoenix.Socket.Transport.html#check_subprotocols/2) Checks the Websocket subprotocols request header against the allowed subprotocols. [code\_reload(conn, endpoint, opts)](Phoenix.Socket.Transport.html#code_reload/3) Runs the code reloader if enabled. [connect\_info(conn, endpoint, keys)](Phoenix.Socket.Transport.html#connect_info/3) Extracts connection information from `conn` and returns a map. [transport\_log(conn, level)](Phoenix.Socket.Transport.html#transport_log/2) Logs the transport request. # [](Phoenix.Socket.Transport.html#types)Types [](Phoenix.Socket.Transport.html#t:state/0) # state() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L95) ``` @type state() :: term() ``` # [](Phoenix.Socket.Transport.html#callbacks)Callbacks [](Phoenix.Socket.Transport.html#c:child_spec/1) # child\_spec(keyword) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L121) ``` @callback child_spec(keyword()) :: :supervisor.child_spec() | :ignore ``` Returns a child specification for socket management. This is invoked only once per socket regardless of the number of transports and should be responsible for setting up any process structure used exclusively by the socket regardless of transports. Each socket connection is started by the transport and the process that controls the socket likely belongs to the transport. However, some sockets spawn new processes, such as [`Phoenix.Socket`](Phoenix.Socket.html) which spawns channels, and this gives the ability to start a supervision tree associated to the socket. It receives the socket options from the endpoint, for example: ``` socket "/my_app", MyApp.Socket, shutdown: 5000 ``` means `child_spec([shutdown: 5000])` will be invoked. `:ignore` means no child spec is necessary for this socket. [](Phoenix.Socket.Transport.html#c:connect/1) # connect(transport\_info) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L162) ``` @callback connect(transport_info :: map()) :: {:ok, state()} | {:error, term()} | :error ``` Connects to the socket. The transport passes a map of metadata and the socket returns `{:ok, state}`, `{:error, reason}` or `:error`. The state must be stored by the transport and returned in all future operations. When `{:error, reason}` is returned, some transports - such as WebSockets - allow customizing the response based on `reason` via a custom `:error_handler`. This function is used for authorization purposes and it may be invoked outside of the process that effectively runs the socket. In the default [`Phoenix.Socket`](Phoenix.Socket.html) implementation, the metadata expects the following keys: - `:endpoint` - the application endpoint - `:transport` - the transport name - `:params` - the connection parameters - `:options` - a keyword list of transport options, often given by developers when configuring the transport. It must include a `:serializer` field with the list of serializers and their requirements [](Phoenix.Socket.Transport.html#c:drainer_spec/1) # drainer\_spec(keyword) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L133)(optional) ``` @callback drainer_spec(keyword()) :: :supervisor.child_spec() | :ignore ``` Returns a child specification for terminating the socket. This is a process that is started late in the supervision tree with the specific goal of draining connections on application shutdown. Similar to `child_spec/1`, it receives the socket options from the endpoint. [](Phoenix.Socket.Transport.html#c:handle_control/2) # handle\_control({}, state) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L210)(optional) ``` @callback handle_control( {message :: term(), opts :: keyword()}, state() ) :: {:ok, state()} | {:reply, :ok | :error, {opcode :: atom(), message :: term()}, state()} | {:stop, reason :: term(), state()} ``` Handles incoming control frames. The message is represented as `{payload, options}`. It must return one of: - `{:ok, state}` - continues the socket with no reply - `{:reply, status, reply, state}` - continues the socket with reply - `{:stop, reason, state}` - stops the socket Control frames only supported when using websockets. The `options` contains an `opcode` key, this will be either `:ping` or `:pong`. If a control frame doesn't have a payload, then the payload value will be `nil`. [](Phoenix.Socket.Transport.html#c:handle_in/2) # handle\_in({}, state) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L187) ``` @callback handle_in( {message :: term(), opts :: keyword()}, state() ) :: {:ok, state()} | {:reply, :ok | :error, {opcode :: atom(), message :: term()}, state()} | {:stop, reason :: term(), state()} ``` Handles incoming socket messages. The message is represented as `{payload, options}`. It must return one of: - `{:ok, state}` - continues the socket with no reply - `{:reply, status, reply, state}` - continues the socket with reply - `{:stop, reason, state}` - stops the socket The `reply` is a tuple contain an `opcode` atom and a message that can be any term. The built-in websocket transport supports both `:text` and `:binary` opcode and the message must be always iodata. Long polling only supports text opcode. [](Phoenix.Socket.Transport.html#c:handle_info/2) # handle\_info(message, state) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L229) ``` @callback handle_info(message :: term(), state()) :: {:ok, state()} | {:push, {opcode :: atom(), message :: term()}, state()} | {:stop, reason :: term(), state()} ``` Handles info messages. The message is a term. It must return one of: - `{:ok, state}` - continues the socket with no reply - `{:push, reply, state}` - continues the socket with reply - `{:stop, reason, state}` - stops the socket The `reply` is a tuple contain an `opcode` atom and a message that can be any term. The built-in websocket transport supports both `:text` and `:binary` opcode and the message must be always iodata. Long polling only supports text opcode. [](Phoenix.Socket.Transport.html#c:init/1) # init(state) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L170) ``` @callback init(state()) :: {:ok, state()} ``` Initializes the socket state. This must be executed from the process that will effectively operate the socket. [](Phoenix.Socket.Transport.html#c:terminate/2) # terminate(reason, state) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L241) ``` @callback terminate(reason :: term(), state()) :: :ok ``` Invoked on termination. If `reason` is `:closed`, it means the client closed the socket. This is considered a `:normal` exit signal, so linked process will not automatically exit. See [`Process.exit/2`](../elixir/Process.html#exit/2) for more details on exit signals. # [](Phoenix.Socket.Transport.html#functions)Functions [](Phoenix.Socket.Transport.html#check_origin/5) # check\_origin(conn, handler, endpoint, opts, sender \\\\ &amp;Plug.Conn.send\_resp/1) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L326) Checks the origin request header against the list of allowed origins. Should be called by transports before connecting when appropriate. If the origin header matches the allowed origins, no origin header was sent or no origin was configured, it will return the given connection. Otherwise a 403 Forbidden response will be sent and the connection halted. It is a noop if the connection has been halted. [](Phoenix.Socket.Transport.html#check_subprotocols/2) # check\_subprotocols(conn, subprotocols) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L385) Checks the Websocket subprotocols request header against the allowed subprotocols. Should be called by transports before connecting when appropriate. If the sec-websocket-protocol header matches the allowed subprotocols, it will put sec-websocket-protocol response header and return the given connection. If no sec-websocket-protocol header was sent it will return the given connection. Otherwise a 403 Forbidden response will be sent and the connection halted. It is a noop if the connection has been halted. [](Phoenix.Socket.Transport.html#code_reload/3) # code\_reload(conn, endpoint, opts) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L295) Runs the code reloader if enabled. [](Phoenix.Socket.Transport.html#connect_info/3) # connect\_info(conn, endpoint, keys) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L462) Extracts connection information from `conn` and returns a map. Keys are retrieved from the optional transport option `:connect_info`. This functionality is transport specific. Please refer to your transports' documentation for more information. The supported keys are: - `:peer_data` - the result of [`Plug.Conn.get_peer_data/1`](../plug/1.15.3/Plug.Conn.html#get_peer_data/1) - `:trace_context_headers` - a list of all trace context headers - `:x_headers` - a list of all request headers that have an "x-" prefix - `:uri` - a `%URI{}` derived from the conn - `:user_agent` - the value of the "user-agent" request header [](Phoenix.Socket.Transport.html#transport_log/2) # transport\_log(conn, level) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket/transport.ex#L308) Logs the transport request. Available for transports that generate a connection. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Socket.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L1 "View Source") Phoenix.Socket behaviour (Phoenix v1.7.18) A socket implementation that multiplexes messages over channels. [`Phoenix.Socket`](Phoenix.Socket.html) is used as a module for establishing a connection between client and server. Once the connection is established, the initial state is stored in the [`Phoenix.Socket`](Phoenix.Socket.html) struct. The same socket can be used to receive events from different transports. Phoenix supports `websocket` and `longpoll` options when invoking [`Phoenix.Endpoint.socket/3`](Phoenix.Endpoint.html#socket/3) in your endpoint. `websocket` is set by default and `longpoll` can also be configured explicitly. ``` socket "/socket", MyAppWeb.Socket, websocket: true, longpoll: false ``` The command above means incoming socket connections can be made via a WebSocket connection. Incoming and outgoing events are routed to channels by topic: ``` channel "room:lobby", MyAppWeb.LobbyChannel ``` See [`Phoenix.Channel`](Phoenix.Channel.html) for more information on channels. ## [](Phoenix.Socket.html#module-socket-behaviour)Socket Behaviour Socket handlers are mounted in Endpoints and must define two callbacks: - `connect/3` - receives the socket params, connection info if any, and authenticates the connection. Must return a [`Phoenix.Socket`](Phoenix.Socket.html) struct, often with custom assigns - `id/1` - receives the socket returned by `connect/3` and returns the id of this connection as a string. The `id` is used to identify socket connections, often to a particular user, allowing us to force disconnections. For sockets requiring no authentication, `nil` can be returned ## [](Phoenix.Socket.html#module-examples)Examples ``` defmodule MyAppWeb.UserSocket do use Phoenix.Socket channel "room:*", MyAppWeb.RoomChannel def connect(params, socket, _connect_info) do {:ok, assign(socket, :user_id, params["user_id"])} end def id(socket), do: "users_socket:#{socket.assigns.user_id}" end # Disconnect all user's socket connections and their multiplexed channels MyAppWeb.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{}) ``` ## [](Phoenix.Socket.html#module-socket-fields)Socket fields - `:id` - The string id of the socket - `:assigns` - The map of socket assigns, default: `%{}` - `:channel` - The current channel module - `:channel_pid` - The channel pid - `:endpoint` - The endpoint module where this socket originated, for example: `MyAppWeb.Endpoint` - `:handler` - The socket module where this socket originated, for example: `MyAppWeb.UserSocket` - `:joined` - If the socket has effectively joined the channel - `:join_ref` - The ref sent by the client when joining - `:ref` - The latest ref sent by the client - `:pubsub_server` - The registered name of the socket's pubsub server - `:topic` - The string topic, for example `"room:123"` - `:transport` - An identifier for the transport, used for logging - `:transport_pid` - The pid of the socket's transport process - `:serializer` - The serializer for socket messages ## [](Phoenix.Socket.html#module-using-options)Using options On `use Phoenix.Socket`, the following options are accepted: - `:log` - the default level to log socket actions. Defaults to `:info`. May be set to `false` to disable it - `:partitions` - each channel is spawned under a supervisor. This option controls how many supervisors will be spawned to handle channels. Defaults to the number of cores. ## [](Phoenix.Socket.html#module-garbage-collection)Garbage collection It's possible to force garbage collection in the transport process after processing large messages. For example, to trigger such from your channels, run: ``` send(socket.transport_pid, :garbage_collect) ``` Alternatively, you can configure your endpoint socket to trigger more fullsweep garbage collections more frequently, by setting the `:fullsweep_after` option for websockets. See [`Phoenix.Endpoint.socket/3`](Phoenix.Endpoint.html#socket/3) for more info. ## [](Phoenix.Socket.html#module-client-server-communication)Client-server communication The encoding of server data and the decoding of client data is done according to a serializer, defined in [`Phoenix.Socket.Serializer`](Phoenix.Socket.Serializer.html). By default, JSON encoding is used to broker messages to and from clients. The serializer `decode!` function must return a [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) which is forwarded to channels except: - `"heartbeat"` events in the "phoenix" topic - should just emit an OK reply - `"phx_join"` on any topic - should join the topic - `"phx_leave"` on any topic - should leave the topic Each message also has a `ref` field which is used to track responses. The server may send messages or replies back. For messages, the ref uniquely identifies the message. For replies, the ref matches the original message. Both data-types also include a join\_ref that uniquely identifies the currently joined channel. The [`Phoenix.Socket`](Phoenix.Socket.html) implementation may also send special messages and replies: - `"phx_error"` - in case of errors, such as a channel process crashing, or when attempting to join an already joined channel - `"phx_close"` - the channel was gracefully closed Phoenix ships with a JavaScript implementation of both websocket and long polling that interacts with Phoenix.Socket and can be used as reference for those interested in implementing custom clients. ## [](Phoenix.Socket.html#module-custom-sockets-and-transports)Custom sockets and transports See the [`Phoenix.Socket.Transport`](Phoenix.Socket.Transport.html) documentation for more information on writing your own socket that does not leverage channels or for writing your own transports that interacts with other sockets. ## [](Phoenix.Socket.html#module-custom-channels)Custom channels You can list any module as a channel as long as it implements a `child_spec/1` function. The `child_spec/1` function receives the caller as argument and it must return a child spec that initializes a process. Once the process is initialized, it will receive the following message: ``` {Phoenix.Channel, auth_payload, from, socket} ``` A custom channel implementation MUST invoke `GenServer.reply(from, {:ok | :error, reply_payload})` during its initialization with a custom `reply_payload` that will be sent as a reply to the client. Failing to do so will block the socket forever. A custom channel receives [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) structs as regular messages from the transport. Replies to those messages and custom messages can be sent to the socket at any moment by building an appropriate [`Phoenix.Socket.Reply`](Phoenix.Socket.Reply.html) and [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) structs, encoding them with the serializer and dispatching the serialized result to the transport. For example, to handle "phx\_leave" messages, which is recommended to be handled by all channel implementations, one may do: ``` def handle_info( %Message{topic: topic, event: "phx_leave"} = message, %{topic: topic, serializer: serializer, transport_pid: transport_pid} = socket ) do send transport_pid, serializer.encode!(build_leave_reply(message)) {:stop, {:shutdown, :left}, socket} end ``` A special message delivered to all channels is a Broadcast with event "phx\_drain", which is sent when draining the socket during application shutdown. Typically it is handled by sending a drain message to the transport, causing it to shutdown: ``` def handle_info( %Broadcast{event: "phx_drain"}, %{transport_pid: transport_pid} = socket ) do send(transport_pid, :socket_drain) {:stop, {:shutdown, :draining}, socket} end ``` We also recommend all channels to monitor the `transport_pid` on `init` and exit if the transport exits. We also advise to rewrite `:normal` exit reasons (usually due to the socket being closed) to the `{:shutdown, :closed}` to guarantee links are broken on the channel exit (as a `:normal` exit does not break links): ``` def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do reason = if reason == :normal, do: {:shutdown, :closed}, else: reason {:stop, reason, socket} end ``` Any process exit is treated as an error by the socket layer unless a `{:socket_close, pid, reason}` message is sent to the socket before shutdown. Custom channel implementations cannot be tested with [`Phoenix.ChannelTest`](Phoenix.ChannelTest.html). # [](Phoenix.Socket.html#summary)Summary ## [Types](Phoenix.Socket.html#types) [t()](Phoenix.Socket.html#t:t/0) ## [Callbacks](Phoenix.Socket.html#callbacks) [connect(params, t)](Phoenix.Socket.html#c:connect/2) Shortcut version of `connect/3` which does not receive `connect_info`. [connect(params, t, connect\_info)](Phoenix.Socket.html#c:connect/3) Receives the socket params and authenticates the connection. [id(t)](Phoenix.Socket.html#c:id/1) Identifies the socket connection. ## [Functions](Phoenix.Socket.html#functions) [assign(socket, attrs)](Phoenix.Socket.html#assign/2) [assign(socket, key, value)](Phoenix.Socket.html#assign/3) Adds key-value pairs to socket assigns. [channel(topic\_pattern, module, opts \\\\ \[\])](Phoenix.Socket.html#channel/3) Defines a channel matching the given topic and transports. # [](Phoenix.Socket.html#types)Types [](Phoenix.Socket.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L274) ``` @type t() :: %Phoenix.Socket{ assigns: map(), channel: atom(), channel_pid: pid(), endpoint: atom(), handler: atom(), id: String.t() | nil, join_ref: term(), joined: boolean(), private: map(), pubsub_server: atom(), ref: term(), serializer: atom(), topic: String.t(), transport: atom(), transport_pid: pid() } ``` # [](Phoenix.Socket.html#callbacks)Callbacks [](Phoenix.Socket.html#c:connect/2) # connect(params, t) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L231)(optional) ``` @callback connect(params :: map(), t()) :: {:ok, t()} | {:error, term()} | :error ``` Shortcut version of `connect/3` which does not receive `connect_info`. Provided for backwards compatibility. [](Phoenix.Socket.html#c:connect/3) # connect(params, t, connect\_info) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L223)(optional) ``` @callback connect(params :: map(), t(), connect_info :: map()) :: {:ok, t()} | {:error, term()} | :error ``` Receives the socket params and authenticates the connection. ## [](Phoenix.Socket.html#c:connect/3-socket-params-and-assigns)Socket params and assigns Socket params are passed from the client and can be used to verify and authenticate a user. After verification, you can put default assigns into the socket that will be set for all channels, ie ``` {:ok, assign(socket, :user_id, verified_user_id)} ``` To deny connection, return `:error` or `{:error, term}`. To control the response the client receives in that case, [define an error handler in the websocket configuration](Phoenix.Endpoint.html#socket/3-websocket-configuration). See [`Phoenix.Token`](Phoenix.Token.html) documentation for examples in performing token verification on connect. [](Phoenix.Socket.html#c:id/1) # id(t) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L247) ``` @callback id(t()) :: String.t() | nil ``` Identifies the socket connection. Socket IDs are topics that allow you to identify all sockets for a given user: ``` def id(socket), do: "users_socket:#{socket.assigns.user_id}" ``` Would allow you to broadcast a `"disconnect"` event and terminate all active sockets and channels for a given user: ``` MyAppWeb.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{}) ``` Returning `nil` makes this socket anonymous. # [](Phoenix.Socket.html#functions)Functions [](Phoenix.Socket.html#assign/2) # assign(socket, attrs) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L350) [](Phoenix.Socket.html#assign/3) # assign(socket, key, value) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L346) Adds key-value pairs to socket assigns. A single key-value pair may be passed, a keyword list or map of assigns may be provided to be merged into existing socket assigns. ## [](Phoenix.Socket.html#assign/3-examples)Examples ``` iex> assign(socket, :name, "Elixir") iex> assign(socket, name: "Elixir", logo: "💧") ``` [](Phoenix.Socket.html#channel/3) # channel(topic\_pattern, module, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/socket.ex#L381)(macro) Defines a channel matching the given topic and transports. - `topic_pattern` - The string pattern, for example `"room:*"`, `"users:*"`, or `"system"` - `module` - The channel module handler, for example `MyAppWeb.RoomChannel` - `opts` - The optional list of options, see below ## [](Phoenix.Socket.html#channel/3-options)Options - `:assigns` - the map of socket assigns to merge into the socket on join ## [](Phoenix.Socket.html#channel/3-examples)Examples ``` channel "topic1:*", MyChannel ``` ## [](Phoenix.Socket.html#channel/3-topic-patterns)Topic Patterns The `channel` macro accepts topic patterns in two flavors. A splat (the `*` character) argument can be provided as the last character to indicate a `"topic:subtopic"` match. If a plain string is provided, only that topic will match the channel handler. Most use-cases will use the `"topic:*"` pattern to allow more versatile topic scoping. See [`Phoenix.Channel`](Phoenix.Channel.html) for more information [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.Token.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L1 "View Source") Phoenix.Token (Phoenix v1.7.18) Conveniences to sign/encrypt data inside tokens for use in Channels, API authentication, and more. The data stored in the token is signed to prevent tampering, and is optionally encrypted. This means that, so long as the key (see below) remains secret, you can be assured that the data stored in the token has not been tampered with by a third party. However, unless the token is encrypted, it is not safe to use this token to store private information, such as a user's sensitive identification data, as it can be trivially decoded. If the token is encrypted, its contents will be kept secret from the client, but it is still a best practice to encode as little secret information as possible, to minimize the impact of key leakage. ## [](Phoenix.Token.html#module-example)Example When generating a unique token for use in an API or Channel it is advised to use a unique identifier for the user, typically the id from a database. For example: ``` iex> user_id = 1 iex> token = Phoenix.Token.sign(MyAppWeb.Endpoint, "user auth", user_id) iex> Phoenix.Token.verify(MyAppWeb.Endpoint, "user auth", token, max_age: 86400) {:ok, 1} ``` In that example we have a user's id, we generate a token and verify it using the secret key base configured in the given `endpoint`. We guarantee the token will only be valid for one day by setting a max age (recommended). The first argument to [`sign/4`](Phoenix.Token.html#sign/4), [`verify/4`](Phoenix.Token.html#verify/4), [`encrypt/4`](Phoenix.Token.html#encrypt/4), and [`decrypt/4`](Phoenix.Token.html#decrypt/4) can be one of: - the module name of a Phoenix endpoint (shown above) - where the secret key base is extracted from the endpoint - [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) - where the secret key base is extracted from the endpoint stored in the connection - [`Phoenix.Socket`](Phoenix.Socket.html) or `Phoenix.LiveView.Socket` - where the secret key base is extracted from the endpoint stored in the socket - a string, representing the secret key base itself. A key base with at least 20 randomly generated characters should be used to provide adequate entropy The second argument is a [cryptographic salt](https://en.wikipedia.org/wiki/Salt_%28cryptography%29) which must be the same in both calls to [`sign/4`](Phoenix.Token.html#sign/4) and [`verify/4`](Phoenix.Token.html#verify/4), or both calls to [`encrypt/4`](Phoenix.Token.html#encrypt/4) and [`decrypt/4`](Phoenix.Token.html#decrypt/4). For instance, it may be called "user auth" and treated as namespace when generating a token that will be used to authenticate users on channels or on your APIs. The third argument can be any term (string, int, list, etc.) that you wish to codify into the token. Upon valid verification, this same term will be extracted from the token. ## [](Phoenix.Token.html#module-usage)Usage Once a token is signed, we can send it to the client in multiple ways. One is via the meta tag: ``` <%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "user auth", @current_user.id) %> ``` Or an endpoint that returns it: ``` def create(conn, params) do user = User.create(params) render(conn, "user.json", %{token: Phoenix.Token.sign(conn, "user auth", user.id), user: user}) end ``` Once the token is sent, the client may now send it back to the server as an authentication mechanism. For example, we can use it to authenticate a user on a Phoenix channel: ``` defmodule MyApp.UserSocket do use Phoenix.Socket def connect(%{"token" => token}, socket, _connect_info) do case Phoenix.Token.verify(socket, "user auth", token, max_age: 86400) do {:ok, user_id} -> socket = assign(socket, :user, Repo.get!(User, user_id)) {:ok, socket} {:error, _} -> :error end end def connect(_params, _socket, _connect_info), do: :error end ``` In this example, the phoenix.js client will send the token in the `connect` command which is then validated by the server. [`Phoenix.Token`](Phoenix.Token.html) can also be used for validating APIs, handling password resets, e-mail confirmation and more. # [](Phoenix.Token.html#summary)Summary ## [Types](Phoenix.Token.html#types) [context()](Phoenix.Token.html#t:context/0) [max\_age\_opt()](Phoenix.Token.html#t:max_age_opt/0) [shared\_opt()](Phoenix.Token.html#t:shared_opt/0) [signed\_at\_opt()](Phoenix.Token.html#t:signed_at_opt/0) ## [Functions](Phoenix.Token.html#functions) [decrypt(context, secret, token, opts \\\\ \[\])](Phoenix.Token.html#decrypt/4) Decrypts the original data from the token and verifies its integrity. [encrypt(context, secret, data, opts \\\\ \[\])](Phoenix.Token.html#encrypt/4) Encodes, encrypts, and signs data into a token you can send to clients. Its usage is identical to that of [`sign/4`](Phoenix.Token.html#sign/4), but the data is extracted using [`decrypt/4`](Phoenix.Token.html#decrypt/4), rather than [`verify/4`](Phoenix.Token.html#verify/4). [sign(context, salt, data, opts \\\\ \[\])](Phoenix.Token.html#sign/4) Encodes and signs data into a token you can send to clients. [verify(context, salt, token, opts \\\\ \[\])](Phoenix.Token.html#verify/4) Decodes the original data from the token and verifies its integrity. # [](Phoenix.Token.html#types)Types [](Phoenix.Token.html#t:context/0) # context() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L102) ``` @type context() :: Plug.Conn.t() | %{:endpoint => atom(), optional(atom()) => any()} | atom() | binary() ``` [](Phoenix.Token.html#t:max_age_opt/0) # max\_age\_opt() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L113) ``` @type max_age_opt() :: {:max_age, pos_integer() | :infinity} ``` [](Phoenix.Token.html#t:shared_opt/0) # shared\_opt() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L108) ``` @type shared_opt() :: {:key_iterations, pos_integer()} | {:key_length, pos_integer()} | {:key_digest, :sha256 | :sha384 | :sha512} ``` [](Phoenix.Token.html#t:signed_at_opt/0) # signed\_at\_opt() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L114) ``` @type signed_at_opt() :: {:signed_at, pos_integer()} ``` # [](Phoenix.Token.html#functions)Functions [](Phoenix.Token.html#decrypt/4) # decrypt(context, secret, token, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L245) ``` @spec decrypt(context(), binary(), binary(), [shared_opt() | max_age_opt()]) :: term() ``` Decrypts the original data from the token and verifies its integrity. Its usage is identical to [`verify/4`](Phoenix.Token.html#verify/4) but for encrypted tokens. ## [](Phoenix.Token.html#decrypt/4-options)Options - `:key_iterations` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 1000 - `:key_length` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 32 - `:key_digest` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to `:sha256` - `:max_age` - verifies the token only if it has been generated "max age" ago in seconds. Defaults to the max age signed in the token by [`encrypt/4`](Phoenix.Token.html#encrypt/4). [](Phoenix.Token.html#encrypt/4) # encrypt(context, secret, data, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L160) ``` @spec encrypt(context(), binary(), term(), [ shared_opt() | max_age_opt() | signed_at_opt() ]) :: binary() ``` Encodes, encrypts, and signs data into a token you can send to clients. Its usage is identical to that of [`sign/4`](Phoenix.Token.html#sign/4), but the data is extracted using [`decrypt/4`](Phoenix.Token.html#decrypt/4), rather than [`verify/4`](Phoenix.Token.html#verify/4). ## [](Phoenix.Token.html#encrypt/4-options)Options - `:key_iterations` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 1000 - `:key_length` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 32 - `:key_digest` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to `:sha256` - `:signed_at` - set the timestamp of the token in seconds. Defaults to `System.os_time(:millisecond)` - `:max_age` - the default maximum age of the token. Defaults to 86400 seconds (1 day) and it may be overridden on [`decrypt/4`](Phoenix.Token.html#decrypt/4). [](Phoenix.Token.html#sign/4) # sign(context, salt, data, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L134) ``` @spec sign(context(), binary(), term(), [ shared_opt() | max_age_opt() | signed_at_opt() ]) :: binary() ``` Encodes and signs data into a token you can send to clients. ## [](Phoenix.Token.html#sign/4-options)Options - `:key_iterations` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 1000 - `:key_length` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 32 - `:key_digest` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to `:sha256` - `:signed_at` - set the timestamp of the token in seconds. Defaults to `System.os_time(:millisecond)` - `:max_age` - the default maximum age of the token. Defaults to 86400 seconds (1 day) and it may be overridden on [`verify/4`](Phoenix.Token.html#verify/4). [](Phoenix.Token.html#verify/4) # verify(context, salt, token, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/token.ex#L221) ``` @spec verify(context(), binary(), binary(), [shared_opt() | max_age_opt()]) :: {:ok, term()} | {:error, :expired | :invalid | :missing} ``` Decodes the original data from the token and verifies its integrity. ## [](Phoenix.Token.html#verify/4-examples)Examples In this scenario we will create a token, sign it, then provide it to a client application. The client will then use this token to authenticate requests for resources from the server. See [`Phoenix.Token`](Phoenix.Token.html) summary for more info about creating tokens. ``` iex> user_id = 99 iex> secret = "kjoy3o1zeidquwy1398juxzldjlksahdk3" iex> namespace = "user auth" iex> token = Phoenix.Token.sign(secret, namespace, user_id) ``` The mechanism for passing the token to the client is typically through a cookie, a JSON response body, or HTTP header. For now, assume the client has received a token it can use to validate requests for protected resources. When the server receives a request, it can use [`verify/4`](Phoenix.Token.html#verify/4) to determine if it should provide the requested resources to the client: ``` iex> Phoenix.Token.verify(secret, namespace, token, max_age: 86400) {:ok, 99} ``` In this example, we know the client sent a valid token because [`verify/4`](Phoenix.Token.html#verify/4) returned a tuple of type `{:ok, user_id}`. The server can now proceed with the request. However, if the client had sent an expired token, an invalid token, or `nil`, [`verify/4`](Phoenix.Token.html#verify/4) would have returned an error instead: ``` iex> Phoenix.Token.verify(secret, namespace, expired, max_age: 86400) {:error, :expired} iex> Phoenix.Token.verify(secret, namespace, invalid, max_age: 86400) {:error, :invalid} iex> Phoenix.Token.verify(secret, namespace, nil, max_age: 86400) {:error, :missing} ``` ## [](Phoenix.Token.html#verify/4-options)Options - `:key_iterations` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 1000 - `:key_length` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to 32 - `:key_digest` - option passed to [`Plug.Crypto.KeyGenerator`](../plug_crypto/2.0.0/Plug.Crypto.KeyGenerator.html) when generating the encryption and signing keys. Defaults to `:sha256` - `:max_age` - verifies the token only if it has been generated "max age" ago in seconds. Defaults to the max age signed in the token by [`sign/4`](Phoenix.Token.html#sign/4). [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.VerifiedRoutes.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L1 "View Source") Phoenix.VerifiedRoutes (Phoenix v1.7.18) Provides route generation with compile-time verification. Use of the `sigil_p` macro allows paths and URLs throughout your application to be compile-time verified against your Phoenix router(s). For example, the following path and URL usages: ``` <.link href={~p"/sessions/new"} method="post">Log in</.link> redirect(to: url(~p"/posts/#{post}")) ``` Will be verified against your standard [`Phoenix.Router`](Phoenix.Router.html) definitions: ``` get "/posts/:post_id", PostController, :show post "/sessions/new", SessionController, :create ``` Unmatched routes will issue compiler warnings: ``` warning: no route path for AppWeb.Router matches "/postz/#{post}" lib/app_web/controllers/post_controller.ex:100: AppWeb.PostController.show/2 ``` Additionally, interpolated ~p values are encoded via the [`Phoenix.Param`](Phoenix.Param.html) protocol. For example, a `%Post{}` struct in your application may derive the [`Phoenix.Param`](Phoenix.Param.html) protocol to generate slug-based paths rather than ID based ones. This allows you to use `~p"/posts/#{post}"` rather than `~p"/posts/#{post.slug}"` throughout your application. See the [`Phoenix.Param`](Phoenix.Param.html) documentation for more details. Query strings are also supported in verified routes, either in traditional query string form: ``` ~p"/posts?page=#{page}" ``` Or as a keyword list or map of values: ``` params = %{page: 1, direction: "asc"} ~p"/posts?#{params}" ``` Like path segments, query strings params are proper URL encoded and may be interpolated directly into the ~p string. ## [](Phoenix.VerifiedRoutes.html#module-options)Options To verify routes in your application modules, such as controller, templates, and views, `use Phoenix.VerifiedRoutes`, which supports the following options: - `:router` - The required router to verify ~p paths against - `:endpoint` - The optional endpoint for ~p script\_name and URL generation - `:statics` - The optional list of static directories to treat as verified paths For example: ``` use Phoenix.VerifiedRoutes, router: AppWeb.Router, endpoint: AppWeb.Endpoint, statics: ~w(images) ``` ## [](Phoenix.VerifiedRoutes.html#module-usage)Usage The majority of path and URL generation needs your application will be met with `~p` and [`url/1`](Phoenix.VerifiedRoutes.html#url/1), where all information necessary to construct the path or URL is provided by the compile-time information stored in the Endpoint and Router passed to `use Phoenix.VerifiedRoutes`. That said, there are some circumstances where [`path/2`](Phoenix.VerifiedRoutes.html#path/2), [`path/3`](Phoenix.VerifiedRoutes.html#path/3), [`url/2`](Phoenix.VerifiedRoutes.html#url/2), and [`url/3`](Phoenix.VerifiedRoutes.html#url/3) are required: - When the runtime values of the `%Plug.Conn{}`, `%Phoenix.LiveSocket{}`, or a `%URI{}` dictate the formation of the path or URL, which happens under the following scenarios: - [`Phoenix.Controller.put_router_url/2`](Phoenix.Controller.html#put_router_url/2) is used to override the endpoint's URL - [`Phoenix.Controller.put_static_url/2`](Phoenix.Controller.html#put_static_url/2) is used to override the endpoint's static URL - When the Router module differs from the one passed to `use Phoenix.VerifiedRoutes`, such as library code, or application code that relies on multiple routers. In such cases, the router module can be provided explicitly to [`path/3`](Phoenix.VerifiedRoutes.html#path/3) and [`url/3`](Phoenix.VerifiedRoutes.html#url/3). ## [](Phoenix.VerifiedRoutes.html#module-tracking-warnings)Tracking Warnings All static path segments must start with forward slash, and you must have a static segment between dynamic interpolations in order for a route to be verified without warnings. For example, the following path generates proper warnings ``` ~p"/media/posts/#{post}" ``` While this one will not allow the compiler to see the full path: ``` type = "posts" ~p"/media/#{type}/#{post}" ``` In such cases, it's better to write a function such as `media_path/1` which branches on different `~p`'s to handle each type. Like any other compilation warning, the Elixir compiler will warn any time the file that a ~p resides in changes, or if the router is changed. To view previously issued warnings for files that lack new changes, the `--all-warnings` flag may be passed to the [`mix compile`](../mix/Mix.Tasks.Compile.html) task. For the following will show all warnings the compiler has previously encountered when compiling the current application code: ``` $ mix compile --all-warnings ``` \*Note: Elixir &gt;= 1.14.0 is required for comprehensive warnings. Older versions will compile properly, but no warnings will be issued. # [](Phoenix.VerifiedRoutes.html#summary)Summary ## [Functions](Phoenix.VerifiedRoutes.html#functions) [path(conn\_or\_socket\_or\_endpoint\_or\_uri, sigil\_p)](Phoenix.VerifiedRoutes.html#path/2) Generates the router path with route verification. [path(conn\_or\_socket\_or\_endpoint\_or\_uri, router, sigil\_p)](Phoenix.VerifiedRoutes.html#path/3) Generates the router path with route verification. [sigil\_p(route, extra)](Phoenix.VerifiedRoutes.html#sigil_p/2) Generates the router path with route verification. [static\_integrity(conn\_or\_socket\_or\_endpoint\_or\_uri, path)](Phoenix.VerifiedRoutes.html#static_integrity/2) Generates an integrity hash to a static asset given its file path. [static\_path(conn\_or\_socket\_or\_endpoint\_or\_uri, path)](Phoenix.VerifiedRoutes.html#static_path/2) Generates path to a static asset given its file path. [static\_url(conn\_or\_socket\_or\_endpoint, path)](Phoenix.VerifiedRoutes.html#static_url/2) Generates url to a static asset given its file path. [unverified\_path(conn\_or\_socket\_or\_endpoint\_or\_uri, router, path, params \\\\ %{})](Phoenix.VerifiedRoutes.html#unverified_path/4) Returns the path with relevant script name prefixes without verification. [unverified\_url(conn\_or\_socket\_or\_endpoint\_or\_uri, path, params \\\\ %{})](Phoenix.VerifiedRoutes.html#unverified_url/3) Returns the URL for the endpoint from the path without verification. [url(sigil\_p)](Phoenix.VerifiedRoutes.html#url/1) Generates the router url with route verification. [url(conn\_or\_socket\_or\_endpoint\_or\_uri, sigil\_p)](Phoenix.VerifiedRoutes.html#url/2) Generates the router url with route verification from the connection, socket, or URI. [url(conn\_or\_socket\_or\_endpoint\_or\_uri, router, sigil\_p)](Phoenix.VerifiedRoutes.html#url/3) Generates the url with route verification from the connection, socket, or URI and router. # [](Phoenix.VerifiedRoutes.html#functions)Functions [](Phoenix.VerifiedRoutes.html#path/2) # path(conn\_or\_socket\_or\_endpoint\_or\_uri, sigil\_p) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L309)(macro) Generates the router path with route verification. See [`sigil_p/2`](Phoenix.VerifiedRoutes.html#sigil_p/2) for more information. Warns when the provided path does not match against the router specified in `use Phoenix.VerifiedRoutes` or the `@router` module attribute. ## [](Phoenix.VerifiedRoutes.html#path/2-examples)Examples ``` import Phoenix.VerifiedRoutes redirect(to: path(conn, ~p"/users/top")) redirect(to: path(conn, ~p"/users/#{@user}")) ~H""" <.link href={path(@uri, "/users?page=#{@page}")}>profile</.link> <.link href={path(@uri, "/users?#{@params}")}>profile</.link> """ ``` [](Phoenix.VerifiedRoutes.html#path/3) # path(conn\_or\_socket\_or\_endpoint\_or\_uri, router, sigil\_p) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L274)(macro) Generates the router path with route verification. See [`sigil_p/2`](Phoenix.VerifiedRoutes.html#sigil_p/2) for more information. Warns when the provided path does not match against the router specified in the router argument. ## [](Phoenix.VerifiedRoutes.html#path/3-examples)Examples ``` import Phoenix.VerifiedRoutes redirect(to: path(conn, MyAppWeb.Router, ~p"/users/top")) redirect(to: path(conn, MyAppWeb.Router, ~p"/users/#{@user}")) ~H""" <.link href={path(@uri, MyAppWeb.Router, "/users?page=#{@page}")}>profile</.link> <.link href={path(@uri, MyAppWeb.Router, "/users?#{@params}")}>profile</.link> """ ``` [](Phoenix.VerifiedRoutes.html#sigil_p/2) # sigil\_p(route, extra) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L202)(macro) Generates the router path with route verification. Interpolated named parameters are encoded via the [`Phoenix.Param`](Phoenix.Param.html) protocol. Warns when the provided path does not match against the router specified in `use Phoenix.VerifiedRoutes` or the `@router` module attribute. ## [](Phoenix.VerifiedRoutes.html#sigil_p/2-examples)Examples ``` use Phoenix.VerifiedRoutes, endpoint: MyAppWeb.Endpoint, router: MyAppWeb.Router redirect(to: ~p"/users/top") redirect(to: ~p"/users/#{@user}") ~H""" <.link href={~p"/users?page=#{@page}"}>profile</.link> <.link href={~p"/users?#{@params}"}>profile</.link> """ ``` [](Phoenix.VerifiedRoutes.html#static_integrity/2) # static\_integrity(conn\_or\_socket\_or\_endpoint\_or\_uri, path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L713) Generates an integrity hash to a static asset given its file path. See `Phoenix.Endpoint.static_integrity/1` for more information. ## [](Phoenix.VerifiedRoutes.html#static_integrity/2-examples)Examples ``` iex> static_integrity(conn, "/assets/app.js") "813dfe33b5c7f8388bccaaa38eec8382" iex> static_integrity(socket, "/assets/app.js") "813dfe33b5c7f8388bccaaa38eec8382" iex> static_integrity(AppWeb.Endpoint, "/assets/app.js") "813dfe33b5c7f8388bccaaa38eec8382" ``` [](Phoenix.VerifiedRoutes.html#static_path/2) # static\_path(conn\_or\_socket\_or\_endpoint\_or\_uri, path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L517) Generates path to a static asset given its file path. See `Phoenix.Endpoint.static_path/1` for more information. ## [](Phoenix.VerifiedRoutes.html#static_path/2-examples)Examples ``` iex> static_path(conn, "/assets/app.js") "/assets/app-813dfe33b5c7f8388bccaaa38eec8382.js" iex> static_path(socket, "/assets/app.js") "/assets/app-813dfe33b5c7f8388bccaaa38eec8382.js" iex> static_path(AppWeb.Endpoint, "/assets/app.js") "/assets/app-813dfe33b5c7f8388bccaaa38eec8382.js" iex> static_path(%URI{path: "/subresource"}, "/assets/app.js") "/subresource/assets/app-813dfe33b5c7f8388bccaaa38eec8382.js" ``` [](Phoenix.VerifiedRoutes.html#static_url/2) # static\_url(conn\_or\_socket\_or\_endpoint, path) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L428) Generates url to a static asset given its file path. See `Phoenix.Endpoint.static_url/0` and `Phoenix.Endpoint.static_path/1` for more information. ## [](Phoenix.VerifiedRoutes.html#static_url/2-examples)Examples ``` iex> static_url(conn, "/assets/app.js") "https://example.com/assets/app-813dfe33b5c7f8388bccaaa38eec8382.js" iex> static_url(socket, "/assets/app.js") "https://example.com/assets/app-813dfe33b5c7f8388bccaaa38eec8382.js" iex> static_url(AppWeb.Endpoint, "/assets/app.js") "https://example.com/assets/app-813dfe33b5c7f8388bccaaa38eec8382.js" ``` [](Phoenix.VerifiedRoutes.html#unverified_path/4) # unverified\_path(conn\_or\_socket\_or\_endpoint\_or\_uri, router, path, params \\\\ %{}) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L549) Returns the path with relevant script name prefixes without verification. ## [](Phoenix.VerifiedRoutes.html#unverified_path/4-examples)Examples ``` iex> unverified_path(conn, AppWeb.Router, "/posts") "/posts" iex> unverified_path(conn, AppWeb.Router, "/posts", page: 1) "/posts?page=1" ``` [](Phoenix.VerifiedRoutes.html#unverified_url/3) # unverified\_url(conn\_or\_socket\_or\_endpoint\_or\_uri, path, params \\\\ %{}) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L462) Returns the URL for the endpoint from the path without verification. ## [](Phoenix.VerifiedRoutes.html#unverified_url/3-examples)Examples ``` iex> unverified_url(conn, "/posts") "https://example.com/posts" iex> unverified_url(conn, "/posts", page: 1) "https://example.com/posts?page=1" ``` [](Phoenix.VerifiedRoutes.html#url/1) # url(sigil\_p) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L364)(macro) Generates the router url with route verification. See [`sigil_p/2`](Phoenix.VerifiedRoutes.html#sigil_p/2) for more information. Warns when the provided path does not match against the router specified in `use Phoenix.VerifiedRoutes` or the `@router` module attribute. ## [](Phoenix.VerifiedRoutes.html#url/1-examples)Examples ``` use Phoenix.VerifiedRoutes, endpoint: MyAppWeb.Endpoint, router: MyAppWeb.Router redirect(to: url(conn, ~p"/users/top")) redirect(to: url(conn, ~p"/users/#{@user}")) ~H""" <.link href={url(@uri, "/users?#{[page: @page]}")}>profile</.link> """ ``` The router may also be provided in cases where you want to verify routes for a router other than the one passed to `use Phoenix.VerifiedRoutes`: ``` redirect(to: url(conn, OtherRouter, ~p"/users")) ``` Forwarded routes are also resolved automatically. For example, imagine you have a forward path to an admin router in your main router: ``` defmodule AppWeb.Router do ... forward "/admin", AppWeb.AdminRouter end defmodule AppWeb.AdminRouter do ... get "/users", AppWeb.Admin.UserController end ``` Forwarded paths in your main application router will be verified as usual, such as `~p"/admin/users"`. [](Phoenix.VerifiedRoutes.html#url/2) # url(conn\_or\_socket\_or\_endpoint\_or\_uri, sigil\_p) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L380)(macro) Generates the router url with route verification from the connection, socket, or URI. See [`url/1`](Phoenix.VerifiedRoutes.html#url/1) for more information. [](Phoenix.VerifiedRoutes.html#url/3) # url(conn\_or\_socket\_or\_endpoint\_or\_uri, router, sigil\_p) [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix/verified_routes.ex#L398)(macro) Generates the url with route verification from the connection, socket, or URI and router. See [`url/1`](Phoenix.VerifiedRoutes.html#url/1) for more information. [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/Phoenix.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix.ex#L1 "View Source") Phoenix (Phoenix v1.7.18) This is the documentation for the Phoenix project. To get started, see our [overview guides](overview.html). # [](Phoenix.html#summary)Summary ## [Functions](Phoenix.html#functions) [json\_library()](Phoenix.html#json_library/0) Returns the configured JSON encoding library for Phoenix. [plug\_init\_mode()](Phoenix.html#plug_init_mode/0) Returns the `:plug_init_mode` that controls when plugs are initialized. # [](Phoenix.html#functions)Functions [](Phoenix.html#json_library/0) # json\_library() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix.ex#L43) Returns the configured JSON encoding library for Phoenix. To customize the JSON library, including the following in your `config/config.exs`: ``` config :phoenix, :json_library, AlternativeJsonLibrary ``` [](Phoenix.html#plug_init_mode/0) # plug\_init\_mode() [](https://github.com/phoenixframework/phoenix/blob/v1.7.18/lib/phoenix.ex#L57) Returns the `:plug_init_mode` that controls when plugs are initialized. We recommend to set it to `:runtime` in development for compilation time improvements. It must be `:compile` in production (the default). This option is passed as the `:init_mode` to [`Plug.Builder.compile/3`](../plug/1.15.3/Plug.Builder.html#compile/3). [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/api-reference.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix "View Source") API Reference Phoenix v1.7.18 ## [](api-reference.html#modules)Modules [Phoenix](Phoenix.html) This is the documentation for the Phoenix project. [Phoenix.ActionClauseError](Phoenix.ActionClauseError.html) [Phoenix.Channel](Phoenix.Channel.html) Defines a Phoenix Channel. [Phoenix.ChannelTest](Phoenix.ChannelTest.html) Conveniences for testing Phoenix channels. [Phoenix.CodeReloader](Phoenix.CodeReloader.html) A plug and module to handle automatic code reloading. [Phoenix.ConnTest](Phoenix.ConnTest.html) Conveniences for testing Phoenix endpoints and connection related helpers. [Phoenix.Controller](Phoenix.Controller.html) Controllers are used to group common functionality in the same (pluggable) module. [Phoenix.Digester.Compressor](Phoenix.Digester.Compressor.html) Defines the [`Phoenix.Digester.Compressor`](Phoenix.Digester.Compressor.html) behaviour for implementing static file compressors. [Phoenix.Digester.Gzip](Phoenix.Digester.Gzip.html) Gzip compressor for Phoenix.Digester [Phoenix.Endpoint](Phoenix.Endpoint.html) Defines a Phoenix endpoint. [Phoenix.Endpoint.Cowboy2Adapter](Phoenix.Endpoint.Cowboy2Adapter.html) The Cowboy2 adapter for Phoenix. [Phoenix.Endpoint.SyncCodeReloadPlug](Phoenix.Endpoint.SyncCodeReloadPlug.html) Wraps an Endpoint, attempting to sync with Phoenix's code reloader if an exception is raising which indicates that we may be in the middle of a reload. [Phoenix.Flash](Phoenix.Flash.html) Provides shared flash access. [Phoenix.Logger](Phoenix.Logger.html) Instrumenter to handle logging of various instrumentation events. [Phoenix.MissingParamError](Phoenix.MissingParamError.html) Raised when a key is expected to be present in the request parameters, but is not. [Phoenix.Naming](Phoenix.Naming.html) Conveniences for inflecting and working with names in Phoenix. [Phoenix.NotAcceptableError](Phoenix.NotAcceptableError.html) Raised when one of the `accept*` headers is not accepted by the server. [Phoenix.Param](Phoenix.Param.html) A protocol that converts data structures into URL parameters. [Phoenix.Presence](Phoenix.Presence.html) Provides Presence tracking to processes and channels. [Phoenix.Router](Phoenix.Router.html) Defines a Phoenix router. [Phoenix.Router.MalformedURIError](Phoenix.Router.MalformedURIError.html) Exception raised when the URI is malformed on matching. [Phoenix.Router.NoRouteError](Phoenix.Router.NoRouteError.html) Exception raised when no route is found. [Phoenix.Socket](Phoenix.Socket.html) A socket implementation that multiplexes messages over channels. [Phoenix.Socket.Broadcast](Phoenix.Socket.Broadcast.html) Defines a message sent from pubsub to channels and vice-versa. [Phoenix.Socket.InvalidMessageError](Phoenix.Socket.InvalidMessageError.html) Raised when the socket message is invalid. [Phoenix.Socket.Message](Phoenix.Socket.Message.html) Defines a message dispatched over transport to channels and vice-versa. [Phoenix.Socket.Reply](Phoenix.Socket.Reply.html) Defines a reply sent from channels to transports. [Phoenix.Socket.Serializer](Phoenix.Socket.Serializer.html) A behaviour that serializes incoming and outgoing socket messages. [Phoenix.Socket.Transport](Phoenix.Socket.Transport.html) Outlines the Socket &lt;-&gt; Transport communication. [Phoenix.Token](Phoenix.Token.html) Conveniences to sign/encrypt data inside tokens for use in Channels, API authentication, and more. [Phoenix.VerifiedRoutes](Phoenix.VerifiedRoutes.html) Provides route generation with compile-time verification. ## [](api-reference.html#mix-tasks)Mix Tasks [mix local.phx](Mix.Tasks.Local.Phx.html) Updates the Phoenix project generator locally. [mix phx](Mix.Tasks.Phx.html) Prints Phoenix tasks and their information. [mix phx.digest](Mix.Tasks.Phx.Digest.html) Digests and compresses static files. [mix phx.digest.clean](Mix.Tasks.Phx.Digest.Clean.html) Removes old versions of compiled assets. [mix phx.gen](Mix.Tasks.Phx.Gen.html) Lists all available Phoenix generators. [mix phx.gen.auth](Mix.Tasks.Phx.Gen.Auth.html) Generates authentication logic and related views for a resource. [mix phx.gen.cert](Mix.Tasks.Phx.Gen.Cert.html) Generates a self-signed certificate for HTTPS testing. [mix phx.gen.channel](Mix.Tasks.Phx.Gen.Channel.html) Generates a Phoenix channel. [mix phx.gen.context](Mix.Tasks.Phx.Gen.Context.html) Generates a context with functions around an Ecto schema. [mix phx.gen.embedded](Mix.Tasks.Phx.Gen.Embedded.html) Generates an embedded Ecto schema for casting/validating data outside the DB. [mix phx.gen.html](Mix.Tasks.Phx.Gen.Html.html) Generates controller with view, templates, schema and context for an HTML resource. [mix phx.gen.json](Mix.Tasks.Phx.Gen.Json.html) Generates controller, JSON view, and context for a JSON resource. [mix phx.gen.live](Mix.Tasks.Phx.Gen.Live.html) Generates LiveView, templates, and context for a resource. [mix phx.gen.notifier](Mix.Tasks.Phx.Gen.Notifier.html) Generates a notifier that delivers emails by default. [mix phx.gen.presence](Mix.Tasks.Phx.Gen.Presence.html) Generates a Presence tracker. [mix phx.gen.release](Mix.Tasks.Phx.Gen.Release.html) Generates release files and optional Dockerfile for release-based deployments. [mix phx.gen.schema](Mix.Tasks.Phx.Gen.Schema.html) Generates an Ecto schema and migration. [mix phx.gen.secret](Mix.Tasks.Phx.Gen.Secret.html) Generates a secret and prints it to the terminal. [mix phx.gen.socket](Mix.Tasks.Phx.Gen.Socket.html) Generates a Phoenix socket handler. [mix phx.new](Mix.Tasks.Phx.New.html) Creates a new Phoenix project. [mix phx.new.ecto](Mix.Tasks.Phx.New.Ecto.html) Creates a new Ecto project within an umbrella project. [mix phx.new.web](Mix.Tasks.Phx.New.Web.html) Creates a new Phoenix web project within an umbrella project. [mix phx.routes](Mix.Tasks.Phx.Routes.html) Prints all routes for the default or a given router. Can also locate the controller function behind a specified url. [mix phx.server](Mix.Tasks.Phx.Server.html) Starts the application by configuring all endpoints servers to run. [Next Page → Changelog for v1.7](changelog.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/api_authentication.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/authentication/api_authentication.md#L1 "View Source") API Authentication > **Requirement**: This guide expects that you have gone through the [`mix phx.gen.auth`](mix_phx_gen_auth.html) guide. This guide shows how to add API authentication on top of [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html). Since the authentication generator already includes a token table, we use it to store API tokens too, following the best security practices. We will break this guide in two parts: augmenting the context and the plug implementation. We will assume that the following [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) command was executed: ``` $ mix phx.gen.auth Accounts User users ``` If you ran something else, it should be trivial to adapt the names. ## [](api_authentication.html#adding-api-functions-to-the-context)Adding API functions to the context Our authentication system will require two functions. One to create the API token and another to verify it. Open up `lib/my_app/accounts.ex` and add these two new functions: ``` ## API @doc """ Creates a new api token for a user. The token returned must be saved somewhere safe. This token cannot be recovered from the database. """ def create_user_api_token(user) do {encoded_token, user_token} = UserToken.build_email_token(user, "api-token") Repo.insert!(user_token) encoded_token end @doc """ Fetches the user by API token. """ def fetch_user_by_api_token(token) do with {:ok, query} <- UserToken.verify_email_token_query(token, "api-token"), %User{} = user <- Repo.one(query) do {:ok, user} else _ -> :error end end ``` The new functions use the existing `UserToken` functionality to store a new type of token called "api-token". Because this is an email token, if the user changes their email, the tokens will be expired. Also notice we called the second function `fetch_user_by_api_token`, instead of `get_user_by_api_token`. Because we want to render different status codes in our API, depending if a user was found or not, we return `{:ok, user}` or `:error`. Elixir's convention is to call these functions `fetch_*`, instead of `get_*` which would usually return `nil` instead of tuples. To make sure our new functions work, let's write tests. Open up `test/my_app/accounts_test.exs` and add this new describe block: ``` describe "create_user_api_token/1 and fetch_user_by_api_token/1" do test "creates and fetches by token" do user = user_fixture() token = Accounts.create_user_api_token(user) assert Accounts.fetch_user_by_api_token(token) == {:ok, user} assert Accounts.fetch_user_by_api_token("invalid") == :error end end ``` If you run the tests, they will actually fail. Something similar to this: ``` 1) test create_user_api_token/1 and fetch_user_by_api_token/1 creates and verify token (Demo.AccountsTest) test/demo/accounts_test.exs:21 ** (FunctionClauseError) no function clause matching in Demo.Accounts.UserToken.days_for_context/1 The following arguments were given to Demo.Accounts.UserToken.days_for_context/1: # 1 "api-token" Attempted function clauses (showing 2 out of 2): defp days_for_context("confirm") defp days_for_context("reset_password") code: assert Accounts.verify_api_token(token) == {:ok, user} stacktrace: (demo 0.1.0) lib/demo/accounts/user_token.ex:129: Demo.Accounts.UserToken.days_for_context/1 (demo 0.1.0) lib/demo/accounts/user_token.ex:114: Demo.Accounts.UserToken.verify_email_token_query/2 (demo 0.1.0) lib/demo/accounts.ex:301: Demo.Accounts.verify_api_token/1 test/demo/accounts_test.exs:24: (test) ``` If you prefer, try looking at the error and fixing it yourself. The explanation will come next. The `UserToken` module expects us to declare the validity of each token and we haven't defined one for "api-token". The length is going to depend on your application and how sensitive it is in terms of security. For this example, let's say the token is valid for 365 days. Open up `lib/my_app/accounts/user_token.ex`, find where `defp days_for_context` is defined, and add a new clause, like this: ``` defp days_for_context("api-token"), do: 365 defp days_for_context("confirm"), do: @confirm_validity_in_days defp days_for_context("reset_password"), do: @reset_password_validity_in_days ``` Now tests should pass and we are ready to move forward! ## [](api_authentication.html#api-authentication-plug)API authentication plug The last part is to add authentication to our API. When we ran [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html), it generated a `MyAppWeb.UserAuth` module with several plugs, which are small functions that receive the `conn` and customize our request/response life-cycle. Open up `lib/my_app_web/user_auth.ex` and add this new function: ``` def fetch_api_user(conn, _opts) do with ["Bearer " <> token] <- get_req_header(conn, "authorization"), {:ok, user} <- Accounts.fetch_user_by_api_token(token) do assign(conn, :current_user, user) else _ -> conn |> send_resp(:unauthorized, "No access for you") |> halt() end end ``` Our function receives the connection and checks if the "authorization" header has been set with "Bearer TOKEN", where "TOKEN" is the value returned by `Accounts.create_user_api_token/1`. In case the token is not valid or there is no such user, we abort the request. Finally, we need to add this `plug` to our pipeline. Open up `lib/my_app_web/router.ex` and you will find a pipeline for API. Let's add our new plug under it, like this: ``` pipeline :api do plug :accepts, ["json"] plug :fetch_api_user end ``` Now you are ready to receive and validate API requests. Feel free to open up `test/my_app_web/user_auth_test.exs` and write your own test. You can use the tests for other plugs as templates! ## [](api_authentication.html#your-turn)Your turn The overall API authentication flow will depend on your application. If you want to use this token in a JavaScript client, you will need to slightly alter the `UserSessionController` to invoke `Accounts.create_user_api_token/1` and return a JSON response and include the token returned it. If you want to provide APIs for 3rd-party users, you will need to allow them to create tokens, and show the result of `Accounts.create_user_api_token/1` to them. They must save these tokens somewhere safe and include them as part of their requests using the "authorization" header. [← Previous Page mix phx.gen.auth](mix_phx_gen_auth.html) [Next Page → Channels](channels.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/authentication/api_authentication.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/asset_management.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/asset_management.md#L1 "View Source") Asset Management Beside producing HTML, most web applications have various assets (JavaScript, CSS, images, fonts and so on). From Phoenix v1.7, new applications use [esbuild](https://esbuild.github.io/) to prepare assets via the [Elixir esbuild wrapper](https://github.com/phoenixframework/esbuild), and [tailwindcss](https://tailwindcss.com) via the [Elixir tailwindcss wrapper](https://github.com/phoenixframework/tailwind) for CSS. The direct integration with `esbuild` and `tailwind` means that newly generated applications do not have dependencies on Node.js or an external build system (e.g. Webpack). Your JavaScript is typically placed at "assets/js/app.js" and `esbuild` will extract it to "priv/static/assets/app.js". In development, this is done automatically via the `esbuild` watcher. In production, this is done by running `mix assets.deploy`. `esbuild` can also handle your CSS files, but by default `tailwind` handles all CSS building. Finally, all other assets, that usually don't have to be preprocessed, go directly to "priv/static". ## [](asset_management.html#third-party-js-packages)Third-party JS packages If you want to import JavaScript dependencies, you have at least three options to add them to your application: 1. Vendor those dependencies inside your project and import them in your "assets/js/app.js" using a relative path: ``` import topbar from "../vendor/topbar" ``` 2. Call `npm install topbar --save` inside your assets directory and `esbuild` will be able to automatically pick them up: ``` import topbar from "topbar" ``` 3. Use Mix to track the dependency from a source repository: ``` # mix.exs {:topbar, github: "buunguyen/topbar", app: false, compile: false} ``` Run [`mix deps.get`](../mix/Mix.Tasks.Deps.Get.html) to fetch the dependency and then import it: ``` import topbar from "topbar" ``` New applications use this third approach to import Heroicons, avoiding vendoring a copy of all icons when you may only use a few or even none, avoiding Node.js and `npm`, and tracking an explicit version that is easy to update thanks to Mix. It is important to note that git dependencies cannot be used by Hex packages, so if you intend to publish your project to Hex, consider vendoring the files instead. ## [](asset_management.html#images-fonts-and-external-files)Images, fonts, and external files If you reference an external file in your CSS or JavaScript files, `esbuild` will attempt to validate and manage them, unless told otherwise. For example, imagine you want to reference `priv/static/images/bg.png`, served at `/images/bg.png`, from your CSS file: ``` body { background-image: url(/images/bg.png); } ``` The above may fail with the following message: ``` error: Could not resolve "/images/bg.png" (mark it as external to exclude it from the bundle) ``` Given the images are already managed by Phoenix, you need to mark all resources from `/images` (and also `/fonts`) as external, as the error message says. This is what Phoenix does by default for new apps since v1.6.1+. In your `config/config.exs`, you will find: ``` args: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), ``` If you need to reference other directories, you need to update the arguments above accordingly. Note running [`mix phx.digest`](Mix.Tasks.Phx.Digest.html) will create digested files for all of the assets in `priv/static`, so your images and fonts are still cache-busted. ## [](asset_management.html#esbuild-plugins)Esbuild plugins Phoenix's default configuration of `esbuild` (via the Elixir wrapper) does not allow you to use [esbuild plugins](https://esbuild.github.io/plugins/). If you want to use an esbuild plugin, for example to compile SASS files to CSS, you can replace the default build system with a custom build script. The following is an example of a custom build using esbuild via Node.JS. First of all, you'll need to install Node.js in development and make it available for your production build step. Then you'll need to add `esbuild` to your Node.js packages and the Phoenix packages. Inside the `assets` directory, run: ``` $ npm install esbuild --save-dev $ npm install ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view --save ``` or, for Yarn: ``` $ yarn add --dev esbuild $ yarn add ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view ``` Next, add a custom JavaScript build script. We'll call the example `assets/build.js`: ``` const esbuild = require("esbuild"); const args = process.argv.slice(2); const watch = args.includes('--watch'); const deploy = args.includes('--deploy'); const loader = { // Add loaders for images/fonts/etc, e.g. { '.svg': 'file' } }; const plugins = [ // Add and configure plugins here ]; // Define esbuild options let opts = { entryPoints: ["js/app.js"], bundle: true, logLevel: "info", target: "es2017", outdir: "../priv/static/assets", external: ["*.css", "fonts/*", "images/*"], nodePaths: ["../deps"], loader: loader, plugins: plugins, }; if (deploy) { opts = { ...opts, minify: true, }; } if (watch) { opts = { ...opts, sourcemap: "inline", }; esbuild .context(opts) .then((ctx) => { ctx.watch(); }) .catch((_error) => { process.exit(1); }); } else { esbuild.build(opts); } ``` This script covers following use cases: - `node build.js`: builds for development &amp; testing (useful on CI) - `node build.js --watch`: like above, but watches for changes continuously - `node build.js --deploy`: builds minified assets for production Modify `config/dev.exs` so that the script runs whenever you change files, replacing the existing `:esbuild` configuration under `watchers`: ``` config :hello, HelloWeb.Endpoint, ... watchers: [ node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)] ], ... ``` Modify the `aliases` task in `mix.exs` to install `npm` packages during `mix setup` and use the new `esbuild` on `mix assets.deploy`: ``` defp aliases do [ setup: ["deps.get", "ecto.setup", "cmd --cd assets npm install"], ..., "assets.deploy": ["cmd --cd assets node build.js --deploy", "phx.digest"] ] end ``` Finally, remove the `esbuild` configuration from `config/config.exs` and remove the dependency from the `deps` function in your `mix.exs`, and you are done! ## [](asset_management.html#alternative-js-build-tools)Alternative JS build tools If you are writing an API or you want to use another asset build tool, you may want to remove the `esbuild` Hex package (see steps below). Then you must follow the additional steps required by the third-party tool. ### [](asset_management.html#remove-esbuild)Remove esbuild 1. Remove the `esbuild` configuration in `config/config.exs` and `config/dev.exs`, 2. Remove the `assets.deploy` task defined in `mix.exs`, 3. Remove the `esbuild` dependency from `mix.exs`, 4. Unlock the `esbuild` dependency: ``` $ mix deps.unlock esbuild ``` ## [](asset_management.html#alternative-css-frameworks)Alternative CSS frameworks By default, Phoenix generates CSS with the `tailwind` library and its default plugins. If you want to use external `tailwind` plugins or another CSS framework, you should replace the `tailwind` Hex package (see steps below). Then you can use an `esbuild` plugin (as outlined above) or even bring a separate framework altogether. ### [](asset_management.html#remove-tailwind)Remove tailwind 1. Remove the `tailwind` configuration in `config/config.exs` and `config/dev.exs`, 2. Remove the `assets.deploy` task defined in `mix.exs`, 3. Remove the `tailwind` dependency from `mix.exs`, 4. Unlock the `tailwind` dependency: ``` $ mix deps.unlock tailwind ``` You may optionally remove and delete the `heroicons` dependency as well. [← Previous Page Telemetry](telemetry.html) [Next Page → mix phx.gen.auth](mix_phx_gen_auth.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/asset_management.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/changelog.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/CHANGELOG.md#L1 "View Source") Changelog for v1.7 See the [upgrade guide](https://gist.github.com/chrismccord/00a6ea2a96bc57df0cce526bd20af8a7) to upgrade from Phoenix 1.6.x. Phoenix v1.7 requires Elixir v1.11+ &amp; Erlang v22.1+. ## [](changelog.html#introduction-of-verified-routes)Introduction of Verified Routes Phoenix 1.7 includes a new [`Phoenix.VerifiedRoutes`](Phoenix.VerifiedRoutes.html) feature which provides `~p` for route generation with compile-time verification. Use of the `sigil_p` macro allows paths and URLs throughout your application to be compile-time verified against your Phoenix router(s). For example the following path and URL usages: ``` <.link href={~p"/sessions/new"} method="post">Log in</.link> redirect(to: url(~p"/posts/#{post}")) ``` Will be verified against your standard [`Phoenix.Router`](Phoenix.Router.html) definitions: ``` get "/posts/:post_id", PostController, :show post "/sessions/new", SessionController, :create ``` Unmatched routes will issue compiler warnings: ``` warning: no route path for AppWeb.Router matches "/postz/#{post}" lib/app_web/controllers/post_controller.ex:100: AppWeb.PostController.show/2 ``` *Note: Elixir v1.14+ is required for comprehensive warnings. Older versions will work properly and warn on new compilations, but changes to the router file will not issue new warnings.* This feature replaces the `Helpers` module generated in your Phoenix router, but helpers will continue to work and be generated. You can disable router helpers by passing the `helpers: false` option to `use Phoenix.Router`. ## [](changelog.html#phx-new-revamp)phx.new revamp The `phx.new` application generator has been improved to rely on function components for both Controller and LiveView rendering, ultimately simplifying the rendering stack of Phoenix applications and providing better reuse. New applications come with a collection of well-documented and accessible core components, styled with Tailwind CSS by default. You can opt-out of Tailwind CSS with the `--no-tailwind` flag (the Tailwind CSS classes are kept in the generated components as reference for future styling). ## [](changelog.html#1-7-18-2024-12-10)1.7.18 (2024-12-10) ### [](changelog.html#enhancements)Enhancements - Use new interpolation syntax in generators - Update gettext in generators to 0.26 ## [](changelog.html#1-7-17-2024-12-03)1.7.17 (2024-12-03) ### [](changelog.html#enhancements-1)Enhancements - Use LiveView 1.0.0 for newly generated applications ## [](changelog.html#1-7-16-2024-12-03)1.7.16 (2024-12-03) ### [](changelog.html#bug-fixes)Bug fixes - Fix required Elixir version in mix.exs ## [](changelog.html#1-7-15-2024-12-02)1.7.15 (2024-12-02) ### [](changelog.html#enhancements-2)Enhancements - Support phoenixframework.org installer ## [](changelog.html#1-7-14-2024-06-18)1.7.14 (2024-06-18) ### [](changelog.html#bug-fixes-1)Bug fixes - Revert "Add `follow_redirect/2` to Phoenix.ConnTest" (#5797) as this conflicts with `follow_redirect/2` in LiveView, which is imported with ConnTest by default ## [](changelog.html#1-7-13-2024-06-18)1.7.13 (2024-06-18) ### [](changelog.html#bug-fixes-2)Bug fixes - Fix Elixir 1.17 warning in Cowboy2Adapter - Fix verified routes emitting diagnostics without file and position ### [](changelog.html#javascript-client-bug-fixes)JavaScript Client Bug Fixes - Fix error when `sessionStorage` is not available on global namespace ### [](changelog.html#enhancements-3)Enhancements - Add `follow_redirect/2` to Phoenix.ConnTest - Use LiveView 1.0.0-rc for newly generated applications - Use new `Phoenix.Component.used_input?` for form errors in generated `core_components.ex` - Allow `mix ecto.setup` from the umbrella root - Bump Endpoint static cache manifest on `config_change` callback ## [](changelog.html#1-7-12-2024-04-11)1.7.12 (2024-04-11) ### [](changelog.html#javascript-client-bug-fixes-1)JavaScript Client Bug Fixes - Fix all unjoined channels from being removed from the socket when channel leave is called on any single unjoined channel instance ### [](changelog.html#enhancements-4)Enhancements - \[phx.gen.auth] Add enhanced session fixation protection. For applications whichs previously used `phx.gen.auth`, the following line can be added to the `renew_session` function in the auth module: ``` defp renew_session(conn) do + delete_csrf_token() conn |> configure_session(renew: true) |> clear_session() ``` *Note*: because the session id is in a http-only cookie by default, the only way to perform this attack prior to this change is if your application was already vulnerable to an XSS attack, which itself grants more escalated "privileges” than the CSRF fixation. ### [](changelog.html#javascript-client-enhancements)JavaScript Client Enhancements - Only memorize longpoll fallback for browser session if WebSocket never had a successful connection ## [](changelog.html#1-7-11-2024-02-01)1.7.11 (2024-02-01) ### [](changelog.html#enhancements-5)Enhancements - \[phx.new] Default to the [Bandit webserver](https://github.com/mtrudel/bandit) for newly generated applications - \[phx.new] Enable longpoll transport by default and auto fallback when websocket fails for newly generated applications ### [](changelog.html#javascript-client-enhancements-1)JavaScript Client Enhancements - Support new `longPollFallbackMs` option to auto fallback when websocket fails to connect - Support new `debug` option to enable verbose logging ### [](changelog.html#deprecations)Deprecations - Deprecate the `c:init/2` callback in endpoints in favor of `config/runtime.exs` or in favor of `{Phoenix.Endpoint, options}` ## [](changelog.html#1-7-10-2023-11-03)1.7.10 (2023-11-03) ### [](changelog.html#bug-fixes-3)Bug fixes - \[phx.new] Fix `CoreComponents.flash` generating incorrect id's causing flash messages to fail to be closed when clicked ### [](changelog.html#enhancements-6)Enhancements - \[Phoenix.Endpoint] Support dynamic port for `Endpoint.url/0` ## [](changelog.html#1-7-9-2023-10-11)1.7.9 (2023-10-11) ### [](changelog.html#bug-fixes-4)Bug fixes - \[Phoenix.CodeReloader] - Fix error in code reloader causing compilation errors - \[phx.new] – fix LiveView debug heex configuration being generated when `--no-html` pas passed ## [](changelog.html#1-7-8-2023-10-09)1.7.8 (2023-10-09) ### [](changelog.html#bug-fixes-5)Bug fixes - \[Phoenix.ChannelTest] Stringify lists when pushing data - \[Phoenix.Controller] Fix filename when sending downloads with non-ascii names - \[Phoenix.CodeReloader] Remove duplicate warnings on recent Elixir versions - \[Phoenix.CodeReloader] Do not crash code reloader if file information is missing from diagnostic - \[Phoenix.Logger] Do not crash when status is atom - \[phx.gen.release] Fix `mix phx.gen.release --docker` failing with `:http_util` error on Elixir v1.15 - \[phx.gen.\*] Skip map inputs in generated forms as there is no trivial matching input - \[phx.new] Fix tailwind/esbuild config and paths in umbrella projects - \[phx.new] Do not render `th` for actions if actions are empty ### [](changelog.html#enhancements-7)Enhancements - \[Phoenix] Allow latest `plug_crypto` - \[Phoenix.Endpoint] Support dynamic socket drainer configuration - \[Phoenix.Logger] Change socket serializer/version logs to warning - \[Phoenix.VerifiedRoutes] Add support for static resources with fragments in `~p` - \[phx.gen.schema] Support `--repo` and `--migration-dir` flags - \[phx.new] Allow `<.input type="checkbox">` without `value` attr in core components - \[phx.new] Allow UTC datetimes in the generators - \[phx.new] Automatically migrate when release starts when using sqlite 3 - \[phx.new] Allow ID to be assigned in flash component - \[phx.new] Add `--adapter` flag for generating application with bandit - \[phx.new] Include DNSCluster for simple clustering - \[phx.routes] Support `--method` option ## [](changelog.html#1-7-7-2023-07-10)1.7.7 (2023-07-10) ### [](changelog.html#enhancements-8)Enhancements - Support incoming binary payloads to channels over longpoll transport ## [](changelog.html#1-7-6-2023-06-16)1.7.6 (2023-06-16) ### [](changelog.html#bug-fixes-6)Bug Fixes - Support websock\_adapter 0.5.3 ### [](changelog.html#enhancements-9)Enhancements - Allow using Phoenix.ChannelTest socket/connect in another process ## [](changelog.html#1-7-5-2023-06-15)1.7.5 (2023-06-15) ### [](changelog.html#bug-fixes-7)Bug Fixes - Fix LongPoll error when draining connections ## [](changelog.html#1-7-4-2023-06-15)1.7.4 (2023-06-15) ### [](changelog.html#bug-fixes-8)Bug Fixes - Fix the WebSocket draining sending incorrect close code when draining causing LiveViews to reload the page instead of reconnecting ## [](changelog.html#1-7-3-2023-05-30)1.7.3 (2023-05-30) ### [](changelog.html#enhancements-10)Enhancements - Use LiveView 0.19 for new apps ### [](changelog.html#bug-fixes-9)Bug Fixes - Fix compilation error page on plug debugger showing obscure error when app fails to compile - Fix warnings being printed twice in route verification ## [](changelog.html#1-7-2-2023-03-20)1.7.2 (2023-03-20) ### [](changelog.html#enhancements-11)Enhancements - \[Endpoint] Add socket draining for batched and orchestrated Channel/LiveView socket shutdown - \[code reloader] Improve the compilation error page to remove horizontal scrolling and include all warnings and errors from compilation - \[phx.new] Support the `--no-tailwind` and `--no-esbuild` flags - \[phx.new] Move heroicons to assets/vendor - \[phx.new] Simplify core modal to use the new JS.exec instruction to reduce footprint - \[sockets] Allow custom csrf\_token\_keys in WebSockets ## [](changelog.html#1-7-1-2023-03-02)1.7.1 (2023-03-02) ### [](changelog.html#enhancements-12)Enhancements - \[phx.new] Embed heroicons in app.css bundle to optimize usage ## [](changelog.html#1-7-0-2023-02-24)1.7.0 (2023-02-24) ### [](changelog.html#bug-fixes-10)Bug Fixes - Fix race conditions in the longpoll transport by batching messages ## [](changelog.html#1-7-0-rc-3-2023-02-15)1.7.0-rc.3 (2023-02-15) ### [](changelog.html#enhancements-13)Enhancements - Use stream based collections for `phx.gen.live` generators - Update `phx.gen.live` generators to use `Phoenix.Component.to_form` ## [](changelog.html#1-7-0-rc-2-2023-01-13)1.7.0-rc.2 (2023-01-13) ### [](changelog.html#bug-fixes-11)Bug Fixes - \[Router] Fix routing bug causing incorrect matching order on similar routes - \[phx.new] Fix installation hanging in some cases ## [](changelog.html#1-7-0-rc-1-2023-01-06)1.7.0-rc.1 (2023-01-06) ### [](changelog.html#enhancements-14)Enhancements - Raise if using verified routes outside of functions - Add tailwind.install/esbuild.install to mix setup ### [](changelog.html#bug-fixes-12)Bug Fixes - \[Presence] fix task shutdown match causing occasional presence errors - \[VerifiedRoutes] Fix expansion causing more compile-time deps than necessary - \[phx.gen.auth] Add password inputs to password reset edit form - \[phx.gen.embedded] Fixes missing :references generation to phx.gen.embedded - Fix textarea rendering in core components - Halt all sockets on intercept to fix longpoll response already sent error ## [](changelog.html#1-7-0-rc-0-2022-11-07)1.7.0-rc.0 (2022-11-07) ### [](changelog.html#deprecations-1)Deprecations - `Phoenix.Controller.get_flash` has been deprecated in favor of the new [`Phoenix.Flash`](Phoenix.Flash.html) module, which provides unified flash access ### [](changelog.html#enhancements-15)Enhancements - \[Router] Add [`Phoenix.VerifiedRoutes`](Phoenix.VerifiedRoutes.html) for `~p`-based route generation with compile-time verification. - \[Router] Support `helpers: false` to `use Phoenix.Router` to disable helper generation - \[Router] Add `--info [url]` switch to `phx.routes` to get route information about a url/path - \[Flash] Add [`Phoenix.Flash`](Phoenix.Flash.html) for unfied flash access ### [](changelog.html#javascript-client-bug-fixes-2)JavaScript Client Bug Fixes - Fix heartbeat being sent after disconnect and causing abnormal disconnects ## [](changelog.html#v1-6)v1.6 The CHANGELOG for v1.6 releases can be found in the [v1.6 branch](https://github.com/phoenixframework/phoenix/blob/v1.6/CHANGELOG.md). [← Previous Page API Reference](api-reference.html) [Next Page → Overview](overview.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/CHANGELOG.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/channels.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/real_time/channels.md#L1 "View Source") Channels > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). Channels are an exciting part of Phoenix that enable soft real-time communication with and between millions of connected clients. Some possible use cases include: - Chat rooms and APIs for messaging apps - Breaking news, like "a goal was scored" or "an earthquake is coming" - Tracking trains, trucks, or race participants on a map - Events in multiplayer games - Monitoring sensors and controlling lights - Notifying a browser that a page's CSS or JavaScript has changed (this is handy in development) Conceptually, Channels are pretty simple. First, clients connect to the server using some transport, like WebSocket. Once connected, they join one or more topics. For example, to interact with a public chat room clients may join a topic called `public_chat`, and to receive updates from a product with ID 7, they may need to join a topic called `product_updates:7`. Clients can push messages to the topics they've joined, and can also receive messages from them. The other way around, Channel servers receive messages from their connected clients, and can push messages to them too. Servers are able to broadcast messages to all clients subscribed to a certain topic. This is illustrated in the following diagram: ``` +----------------+ +--Topic X-->| Mobile Client | | +----------------+ +-------------------+ | +----------------+ | | | +----------------+ | Browser Client |--Topic X-->| Phoenix Server(s) |--+--Topic X-->| Desktop Client | +----------------+ | | | +----------------+ +-------------------+ | | +----------------+ +--Topic X-->| IoT Client | +----------------+ ``` Broadcasts work even if the application runs on several nodes/computers. That is, if two clients have their socket connected to different application nodes and are subscribed to the same topic `T`, both of them will receive messages broadcasted to `T`. That is possible thanks to an internal PubSub mechanism. Channels can support any kind of client: a browser, native app, smart watch, embedded device, or anything else that can connect to a network. All the client needs is a suitable library; see the [Client Libraries](channels.html#client-libraries) section below. Each client library communicates using one of the "transports" that Channels understand. Currently, that's either Websockets or long polling, but other transports may be added in the future. Unlike stateless HTTP connections, Channels support long-lived connections, each backed by a lightweight BEAM process, working in parallel and maintaining its own state. This architecture scales well; Phoenix Channels [can support millions of subscribers with reasonable latency on a single box](https://phoenixframework.org/blog/the-road-to-2-million-websocket-connections), passing hundreds of thousands of messages per second. And that capacity can be multiplied by adding more nodes to the cluster. ## [](channels.html#the-moving-parts)The Moving Parts Although Channels are simple to use from a client perspective, there are a number of components involved in routing messages to clients across a cluster of servers. Let's take a look at them. ### [](channels.html#overview)Overview To start communicating, a client connects to a node (a Phoenix server) using a transport (e.g., Websockets or long polling) and joins one or more channels using that single network connection. One channel server lightweight process is created per client, per topic. Each channel holds onto the `%Phoenix.Socket{}` and can maintain any state it needs within its `socket.assigns`. Once the connection is established, each incoming message from a client is routed, based on its topic, to the correct channel server. If the channel server asks to broadcast a message, that message is sent to the local PubSub, which sends it out to any clients connected to the same server and subscribed to that topic. If there are other nodes in the cluster, the local PubSub also forwards the message to their PubSubs, which send it out to their own subscribers. Because only one message has to be sent per additional node, the performance cost of adding nodes is negligible, while each new node supports many more subscribers. The message flow looks something like this: ``` Channel +-------------------------+ +--------+ route | Sending Client, Topic 1 | | Local | +----------->| Channel.Server |----->| PubSub |--+ +----------------+ | +-------------------------+ +--------+ | | Sending Client |-Transport--+ | | +----------------+ +-------------------------+ | | | Sending Client, Topic 2 | | | | Channel.Server | | | +-------------------------+ | | | | +-------------------------+ | | +----------------+ | Browser Client, Topic 1 | | | | Browser Client |<-------Transport--------| Channel.Server |<----------+ | +----------------+ +-------------------------+ | | | | +-------------------------+ | +----------------+ | Phone Client, Topic 1 | | | Phone Client |<-------Transport--------| Channel.Server |<-+ | +----------------+ +-------------------------+ | +--------+ | | | Remote | | +-------------------------+ +---| PubSub |<-+ +----------------+ | Watch Client, Topic 1 | | +--------+ | | Watch Client |<-------Transport--------| Channel.Server |<-+ | +----------------+ +-------------------------+ | | | +-------------------------+ +--------+ | +----------------+ | IoT Client, Topic 1 | | Remote | | | IoT Client |<-------Transport--------| Channel.Server |<-----| PubSub |<-+ +----------------+ +-------------------------+ +--------+ ``` ### [](channels.html#endpoint)Endpoint In your Phoenix app's `Endpoint` module, a `socket` declaration specifies which socket handler will receive connections on a given URL. ``` socket "/socket", HelloWeb.UserSocket, websocket: true, longpoll: false ``` Phoenix comes with two default transports: websocket and longpoll. You can configure them directly via the `socket` declaration. ### [](channels.html#socket-handlers)Socket Handlers On the client side, you will establish a socket connection to the route above: ``` let socket = new Socket("/socket", {params: {token: window.userToken}}) ``` On the server, Phoenix will invoke `HelloWeb.UserSocket.connect/2`, passing your parameters and the initial socket state. Within the socket, you can authenticate and identify a socket connection and set default socket assigns. The socket is also where you define your channel routes. ### [](channels.html#channel-routes)Channel Routes Channel routes match on the topic string and dispatch matching requests to the given Channel module. The star character `*` acts as a wildcard matcher, so in the following example route, requests for `room:lobby` and `room:123` would both be dispatched to the `RoomChannel`. In your `UserSocket`, you would have: ``` channel "room:*", HelloWeb.RoomChannel ``` ### [](channels.html#channels)Channels Channels handle events from clients, so they are similar to Controllers, but there are two key differences. Channel events can go both directions - incoming and outgoing. Channel connections also persist beyond a single request/response cycle. Channels are the highest level abstraction for real-time communication components in Phoenix. Each Channel will implement one or more clauses of each of these four callback functions - `join/3`, `terminate/2`, `handle_in/3`, and `handle_out/3`. ### [](channels.html#topics)Topics Topics are string identifiers - names that the various layers use in order to make sure messages end up in the right place. As we saw above, topics can use wildcards. This allows for a useful `"topic:subtopic"` convention. Often, you'll compose topics using record IDs from your application layer, such as `"users:123"`. ### [](channels.html#messages)Messages The [`Phoenix.Socket.Message`](Phoenix.Socket.Message.html) module defines a struct with the following keys which denotes a valid message. From the [Phoenix.Socket.Message docs](Phoenix.Socket.Message.html). - `topic` - The string topic or `"topic:subtopic"` pair namespace, such as `"messages"` or `"messages:123"` - `event` - The string event name, for example `"phx_join"` - `payload` - The message payload - `ref` - The unique string ref ### [](channels.html#pubsub)PubSub PubSub is provided by the [`Phoenix.PubSub`](../phoenix_pubsub/2.1.3/Phoenix.PubSub.html) module. Interested parties can receive events by subscribing to topics. Other processes can broadcast events to certain topics. This is useful to broadcast messages on channel and also for application development in general. For instance, letting all connected [live views](https://github.com/phoenixframework/phoenix_live_view) to know that a new comment has been added to a post. The PubSub system takes care of getting messages from one node to another so that they can be sent to all subscribers across the cluster. By default, this is done using [Phoenix.PubSub.PG2](../phoenix_pubsub/Phoenix.PubSub.PG2.html), which uses native BEAM messaging. If your deployment environment does not support distributed Elixir or direct communication between servers, Phoenix also ships with a [Redis Adapter](../phoenix_pubsub_redis/Phoenix.PubSub.Redis.html) that uses Redis to exchange PubSub data. Please see the [Phoenix.PubSub docs](../phoenix_pubsub/Phoenix.PubSub.html) for more information. ### [](channels.html#client-libraries)Client Libraries Any networked device can connect to Phoenix Channels as long as it has a client library. The following libraries exist today, and new ones are always welcome; to write your own, see our how-to guide [Writing a Channels Client](writing_a_channels_client.html). #### Official Phoenix ships with a JavaScript client that is available when generating a new Phoenix project. The documentation for the JavaScript module is available at [https://hexdocs.pm/phoenix/js/](js/index.html); the code is in [multiple js files](https://github.com/phoenixframework/phoenix/blob/main/assets/js/phoenix/). #### 3rd Party - Swift (iOS) - [SwiftPhoenix](https://github.com/davidstump/SwiftPhoenixClient) - Java (Android) - [JavaPhoenixChannels](https://github.com/eoinsha/JavaPhoenixChannels) - Kotlin (Android) - [JavaPhoenixClient](https://github.com/dsrees/JavaPhoenixClient) - C# - [PhoenixSharp](https://github.com/Mazyod/PhoenixSharp) - Elixir - [phoenix\_gen\_socket\_client](https://github.com/Aircloak/phoenix_gen_socket_client) - [slipstream](../slipstream/Slipstream.html) - GDScript (Godot Game Engine) - [GodotPhoenixChannels](https://github.com/alfredbaudisch/GodotPhoenixChannels) ## [](channels.html#tying-it-all-together)Tying it all together Let's tie all these ideas together by building a simple chat application. Make sure [you created a new Phoenix application](up_and_running.html) and now we are ready to generate the `UserSocket`. ### [](channels.html#generating-a-socket)Generating a socket Let's invoke the socket generator to get started: ``` $ mix phx.gen.socket User ``` It will create two files, the client code in `assets/js/user_socket.js` and the server counter-part in `lib/hello_web/channels/user_socket.ex`. After running, the generator will also ask to add the following line to `lib/hello_web/endpoint.ex`: ``` defmodule HelloWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello socket "/socket", HelloWeb.UserSocket, websocket: true, longpoll: false ... end ``` The generator also asks us to import the client code, we will do that later. Next, we will configure our socket to ensure messages get routed to the correct channel. To do that, we'll uncomment the `"room:*"` channel definition: ``` defmodule HelloWeb.UserSocket do use Phoenix.Socket ## Channels channel "room:*", HelloWeb.RoomChannel ... ``` Now, whenever a client sends a message whose topic starts with `"room:"`, it will be routed to our RoomChannel. Next, we'll define a `HelloWeb.RoomChannel` module to manage our chat room messages. ### [](channels.html#joining-channels)Joining Channels The first priority of your channels is to authorize clients to join a given topic. For authorization, we must implement `join/3` in `lib/hello_web/channels/room_channel.ex`. ``` defmodule HelloWeb.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end end ``` For our chat app, we'll allow anyone to join the `"room:lobby"` topic, but any other room will be considered private and special authorization, say from a database, will be required. (We won't worry about private chat rooms for this exercise, but feel free to explore after we finish.) With our channel in place, let's get the client and server talking. The generated `assets/js/user_socket.js` defines a simple client based on the socket implementation that ships with Phoenix. We can use that library to connect to our socket and join our channel, we just need to set our room name to `"room:lobby"` in that file. ``` // assets/js/user_socket.js // ... socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("room:lobby", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket ``` After that, we need to make sure `assets/js/user_socket.js` gets imported into our application JavaScript file. To do that, uncomment this line in `assets/js/app.js`. ``` // ... import "./user_socket.js" ``` Save the file and your browser should auto refresh, thanks to the Phoenix live reloader. If everything worked, we should see "Joined successfully" in the browser's JavaScript console. Our client and server are now talking over a persistent connection. Now let's make it useful by enabling chat. In `lib/hello_web/controllers/page_html/home.html.heex`, we'll replace the existing code with a container to hold our chat messages, and an input field to send them: ``` <div id="messages" role="log" aria-live="polite"></div> <input id="chat-input" type="text"> ``` Now let's add a couple of event listeners to `assets/js/user_socket.js`: ``` // ... let channel = socket.channel("room:lobby", {}) let chatInput = document.querySelector("#chat-input") let messagesContainer = document.querySelector("#messages") chatInput.addEventListener("keypress", event => { if(event.key === 'Enter'){ channel.push("new_msg", {body: chatInput.value}) chatInput.value = "" } }) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket ``` All we had to do is detect that enter was pressed and then `push` an event over the channel with the message body. We named the event `"new_msg"`. With this in place, let's handle the other piece of a chat application, where we listen for new messages and append them to our messages container. ``` // ... let channel = socket.channel("room:lobby", {}) let chatInput = document.querySelector("#chat-input") let messagesContainer = document.querySelector("#messages") chatInput.addEventListener("keypress", event => { if(event.key === 'Enter'){ channel.push("new_msg", {body: chatInput.value}) chatInput.value = "" } }) channel.on("new_msg", payload => { let messageItem = document.createElement("p") messageItem.innerText = `[${Date()}] ${payload.body}` messagesContainer.appendChild(messageItem) }) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket ``` We listen for the `"new_msg"` event using `channel.on`, and then append the message body to the DOM. Now let's handle the incoming and outgoing events on the server to complete the picture. ### [](channels.html#incoming-events)Incoming Events We handle incoming events with `handle_in/3`. We can pattern match on the event names, like `"new_msg"`, and then grab the payload that the client passed over the channel. For our chat application, we simply need to notify all other `room:lobby` subscribers of the new message with `broadcast!/3`. ``` defmodule HelloWeb.RoomChannel do use Phoenix.Channel def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end def handle_in("new_msg", %{"body" => body}, socket) do broadcast!(socket, "new_msg", %{body: body}) {:noreply, socket} end end ``` `broadcast!/3` will notify all joined clients on this `socket`'s topic and invoke their `handle_out/3` callbacks. `handle_out/3` isn't a required callback, but it allows us to customize and filter broadcasts before they reach each client. By default, `handle_out/3` is implemented for us and simply pushes the message on to the client. Hooking into outgoing events allows for powerful message customization and filtering. Let's see how. ### [](channels.html#intercepting-outgoing-events)Intercepting Outgoing Events We won't implement this for our application, but imagine our chat app allowed users to ignore messages about new users joining a room. We could implement that behavior like this, where we explicitly tell Phoenix which outgoing event we want to intercept and then define a `handle_out/3` callback for those events. (Of course, this assumes that we have an `Accounts` context with an `ignoring_user?/2` function, and that we pass a user in via the `assigns` map). It is important to note that the `handle_out/3` callback will be called for every recipient of a message, so more expensive operations like hitting the database should be considered carefully before being included in `handle_out/3`. ``` intercept ["user_joined"] def handle_out("user_joined", msg, socket) do if Accounts.ignoring_user?(socket.assigns[:user], msg.user_id) do {:noreply, socket} else push(socket, "user_joined", msg) {:noreply, socket} end end ``` That's all there is to our basic chat app. Fire up multiple browser tabs and you should see your messages being pushed and broadcasted to all windows! ## [](channels.html#using-token-authentication)Using Token Authentication When we connect, we'll often need to authenticate the client. Fortunately, this is a 4-step process with [Phoenix.Token](Phoenix.Token.html). ### [](channels.html#step-1-assign-a-token-in-the-connection)Step 1 - Assign a Token in the Connection Let's say we have an authentication plug in our app called `OurAuth`. When `OurAuth` authenticates a user, it sets a value for the `:current_user` key in `conn.assigns`. Since the `current_user` exists, we can simply assign the user's token in the connection for use in the layout. We can wrap that behavior up in a private function plug, `put_user_token/2`. This could also be put in its own module as well. To make this all work, we just add `OurAuth` and `put_user_token/2` to the browser pipeline. ``` pipeline :browser do ... plug OurAuth plug :put_user_token end defp put_user_token(conn, _) do if current_user = conn.assigns[:current_user] do token = Phoenix.Token.sign(conn, "user socket", current_user.id) assign(conn, :user_token, token) else conn end end ``` Now our `conn.assigns` contains the `current_user` and `user_token`. ### [](channels.html#step-2-pass-the-token-to-the-javascript)Step 2 - Pass the Token to the JavaScript Next, we need to pass this token to JavaScript. We can do so inside a script tag in `lib/hello_web/components/layouts/app.html.heex` right above the app.js script, as follows: ``` <script>window.userToken = "<%= assigns[:user_token] %>";</script> <script src={~p"/assets/app.js"}></script> ``` ### [](channels.html#step-3-pass-the-token-to-the-socket-constructor-and-verify)Step 3 - Pass the Token to the Socket Constructor and Verify We also need to pass the `:params` to the socket constructor and verify the user token in the `connect/3` function. To do so, edit `lib/hello_web/channels/user_socket.ex`, as follows: ``` def connect(%{"token" => token}, socket, _connect_info) do # max_age: 1209600 is equivalent to two weeks in seconds case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do {:ok, user_id} -> {:ok, assign(socket, :current_user, user_id)} {:error, reason} -> :error end end ``` In our JavaScript, we can use the token set previously when constructing the Socket: ``` let socket = new Socket("/socket", {params: {token: window.userToken}}) ``` We used [`Phoenix.Token.verify/4`](Phoenix.Token.html#verify/4) to verify the user token provided by the client. [`Phoenix.Token.verify/4`](Phoenix.Token.html#verify/4) returns either `{:ok, user_id}` or `{:error, reason}`. We can pattern match on that return in a `case` statement. With a verified token, we set the user's id as the value to `:current_user` in the socket. Otherwise, we return `:error`. ### [](channels.html#step-4-connect-to-the-socket-in-javascript)Step 4 - Connect to the socket in JavaScript With authentication set up, we can connect to sockets and channels from JavaScript. ``` let socket = new Socket("/socket", {params: {token: window.userToken}}) socket.connect() ``` Now that we are connected, we can join channels with a topic: ``` let channel = socket.channel("topic:subtopic", {}) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket ``` Note that token authentication is preferable since it's transport agnostic and well-suited for long running-connections like channels, as opposed to using sessions or other authentication approaches. ## [](channels.html#fault-tolerance-and-reliability-guarantees)Fault Tolerance and Reliability Guarantees Servers restart, networks split, and clients lose connectivity. In order to design robust systems, we need to understand how Phoenix responds to these events and what guarantees it offers. ### [](channels.html#handling-reconnection)Handling Reconnection Clients subscribe to topics, and Phoenix stores those subscriptions in an in-memory ETS table. If a channel crashes, the clients will need to reconnect to the topics they had previously subscribed to. Fortunately, the Phoenix JavaScript client knows how to do this. The server will notify all the clients of the crash. This will trigger each client's `Channel.onError` callback. The clients will attempt to reconnect to the server using an exponential backoff strategy. Once they reconnect, they'll attempt to rejoin the topics they had previously subscribed to. If they are successful, they'll start receiving messages from those topics as before. ### [](channels.html#resending-client-messages)Resending Client Messages Channel clients queue outgoing messages into a `PushBuffer`, and send them to the server when there is a connection. If no connection is available, the client holds on to the messages until it can establish a new connection. With no connection, the client will hold the messages in memory until it establishes a connection, or until it receives a `timeout` event. The default timeout is set to 5000 milliseconds. The client won't persist the messages in the browser's local storage, so if the browser tab closes, the messages will be gone. ### [](channels.html#resending-server-messages)Resending Server Messages Phoenix uses an at-most-once strategy when sending messages to clients. If the client is offline and misses the message, Phoenix won't resend it. Phoenix doesn't persist messages on the server. If the server restarts, unsent messages will be gone. If our application needs stronger guarantees around message delivery, we'll need to write that code ourselves. Common approaches involve persisting messages on the server and having clients request missing messages. For an example, see Chris McCord's Phoenix training: [client code](https://github.com/chrismccord/elixirconf_training/blob/master/web/static/js/app.js#L38-L39) and [server code](https://github.com/chrismccord/elixirconf_training/blob/master/web/channels/document_channel.ex#L13-L19). ## [](channels.html#example-application)Example Application To see an example of the application we just built, checkout the project [phoenix\_chat\_example](https://github.com/chrismccord/phoenix_chat_example). [← Previous Page API Authentication](api_authentication.html) [Next Page → Presence](presence.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/real_time/channels.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/community.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/introduction/community.md#L1 "View Source") Community The Elixir and Phoenix communities are friendly and welcoming. All questions and comments are valuable, so please come join the discussion! There are a number of places to connect with community members at all experience levels. - We're on Libera IRC in the [#elixir](https://web.libera.chat/?channels=#elixir) channel. - Feel free to join and check out the #phoenix channel on [Discord](https://discord.gg/elixir). - Read about [bug reports](https://github.com/phoenixframework/phoenix/blob/main/CONTRIBUTING.md#bug-reports) or open an issue in the Phoenix [issue tracker](https://github.com/phoenixframework/phoenix/issues). - Ask or answer questions about Phoenix on [Elixir Forum](https://elixirforum.com/c/phoenix-forum) or [Stack Overflow](https://stackoverflow.com/questions/tagged/phoenix-framework). - Follow the Phoenix Framework on [Twitter](https://twitter.com/elixirphoenix). The Security Working Group of the Erlang Ecosystem Foundation also publishes in-depth documents about [security best practices for Erlang, Elixir, and Phoenix](https://erlef.github.io/security-wg/web_app_security_best_practices_beam/). ## [](community.html#books)Books - [Programming Phoenix LiveView - Interactive Elixir Web Programming Without Writing Any JavaScript - 2023 (by Bruce Tate and Sophie DeBenedetto)](https://pragprog.com/titles/liveview/programming-phoenix-liveview/) - [Phoenix Tutorial (Phoenix 1.6)](https://thephoenixtutorial.org/) - [Free to read online](https://thephoenixtutorial.org/book) - [Real-Time Phoenix - Build Highly Scalable Systems with Channels (by Stephen Bussey - 2020)](https://pragprog.com/titles/sbsockets/real-time-phoenix/) - [Programming Phoenix 1.4 (by Bruce Tate, Chris McCord, and José Valim - 2019)](https://pragprog.com/titles/phoenix14/programming-phoenix-1-4/) - [Phoenix in Action (by Geoffrey Lessel - 2019)](https://manning.com/books/phoenix-in-action) - [Phoenix Inside Out - Book Series (by Shankar Dhanasekaran - 2017)](https://shankardevy.com/phoenix-book/). First book of the series Mastering Phoenix Framework is [free to read online](https://shankardevy.com/phoenix-inside-out-mpf/) - [Functional Web Development with Elixir, OTP, and Phoenix Rethink the Modern Web App (by Lance Halvorsen - 2017)](https://pragprog.com/titles/lhelph/functional-web-development-with-elixir-otp-and-phoenix/) ## [](community.html#screencasts-courses)Screencasts/Courses - [Phoenix LiveView Free Course (by The Pragmatic Studio - 2023)](https://pragmaticstudio.com/courses/phoenix-liveview) - [Build It With Phoenix video course (by Geoffrey Lessel - 2023)](https://builditwithphoenix.com) - [Free Crash Course: Phoenix LiveView (by Productive Programmer - 2023)](https://www.productiveprogrammer.com/learn-phoenix-liveview-free) - [Phoenix on Rails: Elixir and Phoenix for Ruby on Rails developers (by George Arrowsmith - 2023)](https://phoenixonrails.com) - [Groxio LiveView: Self Study Program (by Bruce Tate - 2020)](https://grox.io/language/liveview/course) - [Alchemist Camp: Learn Elixir and Phoenix by building (2018-2022)](https://alchemist.camp/episodes) - [The Complete Elixir and Phoenix Bootcamp Master Functional Programming Techniques with Elixir and Phoenix while Learning to Build Compelling Web Applications (by Stephen Grider - 2017)](https://www.udemy.com/the-complete-elixir-and-phoenix-bootcamp-and-tutorial/) - [Discover Elixir &amp; Phoenix (by Tristan Edwards - 2017)](https://www.ludu.co/course/discover-elixir-phoenix) - [Phoenix Framework Tutorial (by Tensor Programming - 2017)](https://www.youtube.com/watch?v=irDC1nWKhZ8&index=6&list=PLJbE2Yu2zumAgKjSPyFtvYjP5LqgzafQq) - [Getting Started with Phoenix (by Pluralsight - 2017)](https://www.pluralsight.com/courses/phoenix-getting-started) - [LearnPhoenix.tv: Learn how to Build Fast, Dependable Web Apps with Phoenix (2017)](https://www.learnphoenix.tv/) - [LearnPhoenix.io: Build Scalable, Real-Time Apps with Phoenix, React, and React Native (2016)](https://www.learnphoenix.io/) [← Previous Page Up and Running](up_and_running.html) [Next Page → Packages Glossary](packages_glossary.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/introduction/community.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/components.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/components.md#L1 "View Source") Components and HEEx > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [request life-cycle guide](request_lifecycle.html). The Phoenix endpoint pipeline takes a request, routes it to a controller, and calls a view module to render a template. The view interface from the controller is simple – the controller calls a view function with the connections assigns, and the function's job is to return a HEEx template. We call any function that accepts an `assigns` parameter and returns a HEEx template a *function component*. Function components are defined with the help of the [`Phoenix.Component`](../phoenix_live_view/Phoenix.Component.html) module. Function components are the essential building block for any kind of markup-based template rendering you'll perform in Phoenix. They serve as a shared abstraction for the standard MVC controller-based applications, LiveView applications, layouts, and smaller UI definitions you'll use throughout other templates. In this chapter, we will recap how components were used in previous chapters and find new use cases for them. ## [](components.html#function-components)Function components At the end of the Request life-cycle chapter, we created a template at `lib/hello_web/controllers/hello_html/show.html.heex`, let's open it up: ``` <section> <h2>Hello World, from {@messenger}!</h2> </section> ``` This template, is embedded as part of `HelloHTML`, at `lib/hello_web/controllers/hello_html.ex`: ``` defmodule HelloWeb.HelloHTML do use HelloWeb, :html embed_templates "hello_html/*" end ``` That's simple enough. There's only two lines, `use HelloWeb, :html`. This line calls the `html/0` function defined in `HelloWeb` which sets up the basic imports and configuration for our function components and templates. All of the imports and aliases we make in our module will also be available in our templates. That's because templates are effectively compiled into functions inside their respective module. For example, if you define a function in your module, you will be able to invoke it directly from the template. Let's see this in practice. Imagine we want to refactor our `show.html.heex` to move the rendering of `<h2>Hello World, from {@messenger}!</h2>` to its own function. We can move it to a function component inside `HelloHTML`, let's do so: ``` defmodule HelloWeb.HelloHTML do use HelloWeb, :html embed_templates "hello_html/*" attr :messenger, :string, required: true def greet(assigns) do ~H""" <h2>Hello World, from {@messenger}!</h2> """ end end ``` We declared the attributes we accept via the `attr/3` macro provided by `Phoenix.Component`, then we defined our `greet/1` function which returns the HEEx template. Next we need to update `show.html.heex`: ``` <section> <.greet messenger={@messenger} /> </section> ``` When we reload `http://localhost:4000/hello/Frank`, we should see the same content as before. Since templates are embedded inside the `HelloHTML` module, we were able to invoke the view function simply as `<.greet messenger="..." />`. If the component was defined elsewhere, we can also type `<HelloWeb.HelloHTML.greet messenger="..." />`. By declaring attributes as required, Phoenix will warn at compile time if we call the `<.greet />` component without passing attributes. If an attribute is optional, you can specify the `:default` option with a value: ``` attr :messenger, :string, default: nil ``` Although this is a quick example, it shows the different roles function components play in Phoenix: - Function components can be defined as functions that receive `assigns` as argument and call the `~H` sigil, as we did in `greet/1` - Function components can be embedded from template files, that's how we load `show.html.heex` into `HelloWeb.HelloHTML` - Function components can declare which attributes are expected, which are validated at compilation time - Function components can be directly rendered from controllers - Function components can be directly rendered from other function components, as we called `<.greet messenger={@messenger} />` from `show.html.heex` And there's more. Before we go deeper, let's fully understand the expressive power behind the HEEx template language. ## [](components.html#heex)HEEx Function components and templates files are powered by [the HEEx template language](../phoenix_live_view/Phoenix.Component.html#sigil_H/2), which stands for "HTML+EEx". EEx is an Elixir library that uses `<%= expression %>` to execute Elixir expressions and interpolate their results into arbitrary text templates. HEEx extends EEx for writing HTML templates mixed with Elixir interpolation. We can write Elixir code inside `{...}` for HTML-aware interpolation inside tag attributes and the body. We can also interpolate arbitrary HEEx blocks using EEx interpolation (`<%= ... %>`). We use `@name` to access the key `name` defined inside `assigns`. This is frequently used to display assigns we have set by way of the `@` shortcut. In your controller, if you invoke: ``` render(conn, :show, username: "joe") ``` Then you can access said username in the templates as `{@username}`. In addition to displaying assigns and functions, we can use pretty much any Elixir expression. For example, in order to have conditionals: ``` <%= if some_condition? do %> <p>Some condition is true for user: {@username}</p> <% else %> <p>Some condition is false for user: {@username}</p> <% end %> ``` or even loops: ``` <table> <tr> <th>Number</th> <th>Power</th> </tr> <%= for number <- 1..10 do %> <tr> <td>{number}</td> <td>{number * number}</td> </tr> <% end %> </table> ``` Did you notice the use of `<%= %>` versus `<% %>` above? All expressions that output something to the template **must** use the equals sign (`=`). If this is not included the code will still be executed but nothing will be inserted into the template. HEEx also comes with handy HTML extensions we will learn next. ### [](components.html#html-extensions)HTML extensions Besides allowing interpolation of Elixir expressions via `<%= %>`, `.heex` templates come with HTML-aware extensions. For example, let's see what happens if you try to interpolate a value with "&lt;" or "&gt;" in it, which would lead to HTML injection: ``` {"<b>Bold?</b>"} ``` Once you render the template, you will see the literal `<b>` on the page. This means users cannot inject HTML content on the page. If you want to allow them to do so, you can call `raw`, but do so with extreme care: ``` {raw "<b>Bold?</b>"} ``` Another super power of HEEx templates is validation of HTML and interpolation syntax of attributes. You can write: ``` <div title="My div" class={@class}> <p>Hello {@username}</p> </div> ``` Notice how you could simply use `key={value}`. HEEx will automatically handle special values such as `false` to remove the attribute or a list of classes. To interpolate a dynamic number of attributes in a keyword list or map, do: ``` <div title="My div" {@many_attributes}> <p>Hello {@username}</p> </div> ``` Also, try removing the closing `</div>` or renaming it to `</div-typo>`. HEEx templates will let you know about your error. HEEx also supports shorthand syntax for `if` and `for` expressions via the special `:if` and `:for` attributes. For example, rather than this: ``` <%= if @some_condition do %> <div>...</div> <% end %> ``` You can write: ``` <div :if={@some_condition}>...</div> ``` Likewise, for comprehensions may be written as: ``` <ul> <li :for={item <- @items}>{item.name}</li> </ul> ``` ## [](components.html#layouts)Layouts Layouts are just function components. They are defined in a module, just like all other function component templates. In a newly generated app, this is `lib/hello_web/components/layouts.ex`. You will also find a `layouts` folder with two built-in layouts generated by Phoenix. The default *root layout* is called `root.html.heex`, and it is the layout into which all templates will be rendered by default. The second is the *app layout*, called `app.html.heex`, which is rendered within the root layout and includes our contents. You may be wondering how the string resulting from a rendered view ends up inside a layout. That's a great question! If we look at `lib/hello_web/components/layouts/root.html.heex`, just about at the end of the `<body>`, we will see this. ``` {@inner_content} ``` In other words, after rendering your page, the result is placed in the `@inner_content` assign. Phoenix provides all kinds of conveniences to control which layout should be rendered. For example, the [`Phoenix.Controller`](Phoenix.Controller.html) module provides the `put_root_layout/2` function for us to switch *root layouts*. This takes `conn` as its first argument and a keyword list of formats and their layouts. You can set it to `false` to disable the layout altogether. You can edit the `index` action of `HelloController` in `lib/hello_web/controllers/hello_controller.ex` to look like this. ``` def index(conn, _params) do conn |> put_root_layout(html: false) |> render(:index) end ``` After reloading [http://localhost:4000/hello](http://localhost:4000/hello), we should see a very different page, one with no title or CSS styling at all. To customize the application layout, we invoke a similar function named `put_layout/2`. Let's actually create another layout and render the index template into it. As an example, let's say we had a different layout for the admin section of our application which didn't have the logo image. To do this, copy the existing `app.html.heex` to a new file `admin.html.heex` in the same directory `lib/hello_web/components/layouts`. Then remove everything inside the `<header>...</header>` tags (or change it to whatever you desire) in the new file. Now, in the `index` action of the controller of `lib/hello_web/controllers/hello_controller.ex`, add the following: ``` def index(conn, _params) do conn |> put_layout(html: :admin) |> render(:index) end ``` When we load the page, we should be rendering the admin layout without the header (or a custom one that you wrote). At this point, you may be wondering, why does Phoenix have two layouts? First of all, it gives us flexibility. In practice, we will hardly have multiple root layouts, as they often contain only HTML headers. This allows us to focus on different application layouts with only the parts that changes between them. Second of all, Phoenix ships with a feature called LiveView, which allows us to build rich and real-time user experiences with server-rendered HTML. LiveView is capable of dynamically changing the contents of the page, but it only ever changes the app layout, never the root layout. Check out [the LiveView documentation](../phoenix_live_view.html) to learn more. ## [](components.html#corecomponents)CoreComponents In a new Phoenix application, you will also find a `core_components.ex` module inside the `components` folder. This module is a great example of defining function components to be reused throughout our application. This guarantees that, as our application evolves, our components will look consistent. If you look inside `def html` in `HelloWeb` placed at `lib/hello_web.ex`, you will see that `CoreComponents` are automatically imported into all HTML views via `use HelloWeb, :html`. This is also the reason why `CoreComponents` itself performs `use Phoenix.Component` instead `use HelloWeb, :html` at the top: doing the latter would cause a deadlock as we would try to import `CoreComponents` into itself. CoreComponents also play an important role in Phoenix code generators, as the code generators assume those components are available in order to quickly scaffold your application. In case you want to learn more about all of these pieces, you may: - Explore the generated `CoreComponents` module to learn more from practical examples - Read the official documentation for [`Phoenix.Component`](../phoenix_live_view/Phoenix.Component.html) - Read the official documentation for [HEEx and the ~H sigils](../phoenix_live_view/Phoenix.Component.html#sigil_H/2) [← Previous Page Controllers](controllers.html) [Next Page → Ecto](ecto.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/components.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/contexts.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/contexts.md#L1 "View Source") Contexts > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html). > **Requirement**: This guide expects that you have gone through the [Ecto guide](ecto.html). So far, we've built pages, wired up controller actions through our routers, and learned how Ecto allows data to be validated and persisted. Now it's time to tie it all together by writing web-facing features that interact with our greater Elixir application. When building a Phoenix project, we are first and foremost building an Elixir application. Phoenix's job is to provide a web interface into our Elixir application. Naturally, we compose our applications with modules and functions, but we often assign specific responsibilities to certain modules and give them names: such as controllers, routers, and live views. As everything else, contexts in Phoenix are modules, but with the distinct reponsibility of drawing boundaries and grouping functionality. In other words, they allow us to reason and discuss about application design. ## [](contexts.html#thinking-about-contexts)Thinking about contexts Contexts are dedicated modules that expose and group related functionality. For example, anytime you call Elixir's standard library, be it [`Logger.info/1`](../logger/Logger.html#info/1) or [`Stream.map/2`](../elixir/Stream.html#map/2), you are accessing different contexts. Internally, Elixir's logger is made of multiple modules, but we never interact with those modules directly. We call the [`Logger`](../logger/Logger.html) module the context, exactly because it exposes and groups all of the logging functionality. By giving modules that expose and group related functionality the name **contexts**, we help developers identify these patterns and talk about them. At the end of the day, contexts are just modules, as are your controllers, views, etc. In Phoenix, contexts often encapsulate data access and data validation. They often talk to a database or APIs. Overall, think of them as boundaries to decouple and isolate parts of your application. Let's use these ideas to build out our web application. Our goal is to build an ecommerce system where we can showcase products, allow users to add products to their cart, and complete their orders. > How to read this guide: Using the context generators is a great way for beginners and intermediate Elixir programmers alike to get up and running quickly while thoughtfully writing their applications. This guide focuses on those readers. ### [](contexts.html#adding-a-catalog-context)Adding a Catalog Context An ecommerce platform has wide-reaching coupling across a codebase so it's important to think about writing well-defined modules. With that in mind, our goal is to build a product catalog API that handles creating, updating, and deleting the products available in our system. We'll start off with the basic features of showcasing our products, and we will add shopping cart features later. We'll see how starting with a solid foundation with isolated boundaries allows us to grow our application naturally as we add functionality. Phoenix includes the [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html), [`mix phx.gen.json`](Mix.Tasks.Phx.Gen.Json.html), [`mix phx.gen.live`](Mix.Tasks.Phx.Gen.Live.html), and [`mix phx.gen.context`](Mix.Tasks.Phx.Gen.Context.html) generators that apply the ideas of isolating functionality in our applications into contexts. These generators are a great way to hit the ground running while Phoenix nudges you in the right direction to grow your application. Let's put these tools to use for our new product catalog context. In order to run the context generators, we need to come up with a module name that groups the related functionality that we're building. In the [Ecto guide](ecto.html), we saw how we can use Changesets and Repos to validate and persist user schemas, but we didn't integrate this with our application at large. In fact, we didn't think about where a "user" in our application should live at all. Let's take a step back and think about the different parts of our system. We know that we'll have products to showcase on pages for sale, along with descriptions, pricing, etc. Along with selling products, we know we'll need to support carting, order checkout, and so on. While the products being purchased are related to the cart and checkout processes, showcasing a product and managing the *exhibition* of our products is distinctly different than tracking what a user has placed in their cart or how an order is placed. A `Catalog` context is a natural place for the management of our product details and the showcasing of those products we have for sale. #### Naming things is hard If you're stuck when trying to come up with a context name when the grouped functionality in your system isn't yet clear, you can simply use the plural form of the resource you're creating. For example, a `Products` context for managing products. As you grow your application and the parts of your system become clear, you can simply rename the context to a more refined one. To jump-start our catalog context, we'll use [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html) which creates a context module that wraps up Ecto access for creating, updating, and deleting products, along with web files like controllers and templates for the web interface into our context. Run the following command at your project root: ``` $ mix phx.gen.html Catalog Product products title:string \ description:string price:decimal views:integer * creating lib/hello_web/controllers/product_controller.ex * creating lib/hello_web/controllers/product_html/edit.html.heex * creating lib/hello_web/controllers/product_html/index.html.heex * creating lib/hello_web/controllers/product_html/new.html.heex * creating lib/hello_web/controllers/product_html/show.html.heex * creating lib/hello_web/controllers/product_html/product_form.html.heex * creating lib/hello_web/controllers/product_html.ex * creating test/hello_web/controllers/product_controller_test.exs * creating lib/hello/catalog/product.ex * creating priv/repo/migrations/20210201185747_create_products.exs * creating lib/hello/catalog.ex * injecting lib/hello/catalog.ex * creating test/hello/catalog_test.exs * injecting test/hello/catalog_test.exs * creating test/support/fixtures/catalog_fixtures.ex * injecting test/support/fixtures/catalog_fixtures.ex Add the resource to your browser scope in lib/hello_web/router.ex: resources "/products", ProductController Remember to update your repository by running migrations: $ mix ecto.migrate ``` Phoenix generated the web files as expected in `lib/hello_web/`. We can also see our context files were generated inside a `lib/hello/catalog.ex` file and our product schema in the directory of the same name. Note the difference between `lib/hello` and `lib/hello_web`. We have a `Catalog` module to serve as the public API for product catalog functionality, as well as a `Catalog.Product` struct, which is an Ecto schema for casting and validating product data. Phoenix also provided web and context tests for us, it also included test helpers for creating entities via the `Hello.Catalog` context, which we'll look at later. For now, let's follow the instructions and add the route according to the console instructions, in `lib/hello_web/router.ex`: ``` scope "/", HelloWeb do pipe_through :browser get "/", PageController, :index + resources "/products", ProductController end ``` With the new route in place, Phoenix reminds us to update our repo by running [`mix ecto.migrate`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Migrate.html), but first we need to make a few tweaks to the generated migration in `priv/repo/migrations/*_create_products.exs`: ``` def change do create table(:products) do add :title, :string add :description, :string - add :price, :decimal + add :price, :decimal, precision: 15, scale: 6, null: false - add :views, :integer + add :views, :integer, default: 0, null: false timestamps() end ``` We modified our price column to a specific precision of 15, scale of 6, along with a not-null constraint. This ensures we store currency with proper precision for any mathematical operations we may perform. Next, we added a default value and not-null constraint to our views count. With our changes in place, we're ready to migrate up our database. Let's do that now: ``` $ mix ecto.migrate 14:09:02.260 [info] == Running 20210201185747 Hello.Repo.Migrations.CreateProducts.change/0 forward 14:09:02.262 [info] create table products 14:09:02.273 [info] == Migrated 20210201185747 in 0.0s ``` Before we jump into the generated code, let's start the server with [`mix phx.server`](Mix.Tasks.Phx.Server.html) and visit [http://localhost:4000/products](http://localhost:4000/products). Let's follow the "New Product" link and click the "Save" button without providing any input. We should be greeted with the following output: ``` Oops, something went wrong! Please check the errors below. ``` When we submit the form, we can see all the validation errors inline with the inputs. Nice! Out of the box, the context generator included the schema fields in our form template and we can see our default validations for required inputs are in effect. Let's enter some example product data and resubmit the form: ``` Product created successfully. Title: Metaprogramming Elixir Description: Write Less Code, Get More Done (and Have Fun!) Price: 15.000000 Views: 0 ``` If we follow the "Back" link, we get a list of all products, which should contain the one we just created. Likewise, we can update this record or delete it. Now that we've seen how it works in the browser, it's time to take a look at the generated code. ## [](contexts.html#starting-with-generators)Starting with generators That little [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html) command packed a surprising punch. We got a lot of functionality out-of-the-box for creating, updating, and deleting products in our catalog. This is far from a full-featured app, but remember, generators are first and foremost learning tools and a starting point for you to begin building real features. Code generation can't solve all your problems, but it will teach you the ins and outs of Phoenix and nudge you towards the proper mindset when designing your application. Let's first check out the `ProductController` that was generated in `lib/hello_web/controllers/product_controller.ex`: ``` defmodule HelloWeb.ProductController do use HelloWeb, :controller alias Hello.Catalog alias Hello.Catalog.Product def index(conn, _params) do products = Catalog.list_products() render(conn, :index, products: products) end def new(conn, _params) do changeset = Catalog.change_product(%Product{}) render(conn, :new, changeset: changeset) end def create(conn, %{"product" => product_params}) do case Catalog.create_product(product_params) do {:ok, product} -> conn |> put_flash(:info, "Product created successfully.") |> redirect(to: ~p"/products/#{product}") {:error, %Ecto.Changeset{} = changeset} -> render(conn, :new, changeset: changeset) end end def show(conn, %{"id" => id}) do product = Catalog.get_product!(id) render(conn, :show, product: product) end ... end ``` We've seen how controllers work in our [controller guide](controllers.html), so the code probably isn't too surprising. What is worth noticing is how our controller calls into the `Catalog` context. We can see that the `index` action fetches a list of products with `Catalog.list_products/0`, and how products are persisted in the `create` action with `Catalog.create_product/1`. We haven't yet looked at the catalog context, so we don't yet know how product fetching and creation is happening under the hood – *but that's the point*. Our Phoenix controller is the web interface into our greater application. It shouldn't be concerned with the details of how products are fetched from the database or persisted into storage. We only care about telling our application to perform some work for us. This is great because our business logic and storage details are decoupled from the web layer of our application. If we move to a full-text storage engine later for fetching products instead of a SQL query, our controller doesn't need to be changed. Likewise, we can reuse our context code from any other interface in our application, be it a channel, mix task, or long-running process importing CSV data. In the case of our `create` action, when we successfully create a product, we use [`Phoenix.Controller.put_flash/3`](Phoenix.Controller.html#put_flash/3) to show a success message, and then we redirect to the router's product show page. Conversely, if `Catalog.create_product/1` fails, we render our `"new.html"` template and pass along the Ecto changeset for the template to lift error messages from. Next, let's dig deeper and check out our `Catalog` context in `lib/hello/catalog.ex`: ``` defmodule Hello.Catalog do @moduledoc """ The Catalog context. """ import Ecto.Query, warn: false alias Hello.Repo alias Hello.Catalog.Product @doc """ Returns the list of products. ## Examples iex> list_products() [%Product{}, ...] """ def list_products do Repo.all(Product) end ... end ``` This module will be the public API for all product catalog functionality in our system. For example, in addition to product detail management, we may also handle product category classification and product variants for things like optional sizing, trims, etc. If we look at the `list_products/0` function, we can see the private details of product fetching. And it's super simple. We have a call to `Repo.all(Product)`. We saw how Ecto repo queries worked in the [Ecto guide](ecto.html), so this call should look familiar. Our `list_products` function is a generalized function name specifying the *intent* of our code – namely to list products. The details of that intent where we use our Repo to fetch the products from our PostgreSQL database is hidden from our callers. This is a common theme we'll see re-iterated as we use the Phoenix generators. Phoenix will push us to think about where we have different responsibilities in our application, and then to wrap up those different areas behind well-named modules and functions that make the intent of our code clear, while encapsulating the details. Now we know how data is fetched, but how are products persisted? Let's take a look at the `Catalog.create_product/1` function: ``` @doc """ Creates a product. ## Examples iex> create_product(%{field: value}) {:ok, %Product{}} iex> create_product(%{field: bad_value}) {:error, %Ecto.Changeset{}} """ def create_product(attrs \\ %{}) do %Product{} |> Product.changeset(attrs) |> Repo.insert() end ``` There's more documentation than code here, but a couple of things are important to highlight. First, we can see again that our Ecto Repo is used under the hood for database access. You probably also noticed the call to `Product.changeset/2`. We talked about changesets before, and now we see them in action in our context. If we open up the `Product` schema in `lib/hello/catalog/product.ex`, it will look immediately familiar: ``` defmodule Hello.Catalog.Product do use Ecto.Schema import Ecto.Changeset schema "products" do field :description, :string field :price, :decimal field :title, :string field :views, :integer timestamps() end @doc false def changeset(product, attrs) do product |> cast(attrs, [:title, :description, :price, :views]) |> validate_required([:title, :description, :price, :views]) end end ``` This is just what we saw before when we ran [`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html), except here we see a `@doc false` above our `changeset/2` function. This tells us that while this function is publicly callable, it's not part of the public context API. Callers that build changesets do so via the context API. For example, `Catalog.create_product/1` calls into our `Product.changeset/2` to build the changeset from user input. Callers, such as our controller actions, do not access `Product.changeset/2` directly. All interaction with our product changesets is done through the public `Catalog` context. ## [](contexts.html#adding-catalog-functions)Adding Catalog functions As we've seen, your context modules are dedicated modules that expose and group related functionality. Phoenix generates generic functions, such as `list_products` and `update_product`, but they only serve as a basis for you to grow your business logic and application from. Let's add one of the basic features of our catalog by tracking product page view count. For any ecommerce system, the ability to track how many times a product page has been viewed is essential for marketing, suggestions, ranking, etc. While we could try to use the existing `Catalog.update_product` function, along the lines of `Catalog.update_product(product, %{views: product.views + 1})`, this would not only be prone to race conditions, but it would also require the caller to know too much about our Catalog system. To see why the race condition exists, let's walk through the possible execution of events: Intuitively, you would assume the following events: 1. User 1 loads the product page with count of 13 2. User 1 saves the product page with count of 14 3. User 2 loads the product page with count of 14 4. User 2 saves the product page with count of 15 While in practice this would happen: 1. User 1 loads the product page with count of 13 2. User 2 loads the product page with count of 13 3. User 1 saves the product page with count of 14 4. User 2 saves the product page with count of 14 The race conditions would make this an unreliable way to update the existing table since multiple callers may be updating out of date view values. There's a better way. Let's think of a function that describes what we want to accomplish. Here's how we would like to use it: ``` product = Catalog.inc_page_views(product) ``` That looks great. Our callers will have no confusion over what this function does, and we can wrap up the increment in an atomic operation to prevent race conditions. Open up your catalog context (`lib/hello/catalog.ex`), and add this new function: ``` def inc_page_views(%Product{} = product) do {1, [%Product{views: views}]} = from(p in Product, where: p.id == ^product.id, select: [:views]) |> Repo.update_all(inc: [views: 1]) put_in(product.views, views) end ``` We built a query for fetching the current product given its ID which we pass to `Repo.update_all`. Ecto's `Repo.update_all` allows us to perform batch updates against the database, and is perfect for atomically updating values, such as incrementing our views count. The result of the repo operation returns the number of updated records, along with the selected schema values specified by the `select` option. When we receive the new product views, we use `put_in(product.views, views)` to place the new view count within the product struct. With our context function in place, let's make use of it in our product controller. Update your `show` action in `lib/hello_web/controllers/product_controller.ex` to call our new function: ``` def show(conn, %{"id" => id}) do product = id |> Catalog.get_product!() |> Catalog.inc_page_views() render(conn, :show, product: product) end ``` We modified our `show` action to pipe our fetched product into `Catalog.inc_page_views/1`, which will return the updated product. Then we rendered our template just as before. Let's try it out. Refresh one of your product pages a few times and watch the view count increase. We can also see our atomic update in action in the ecto debug logs: ``` [debug] QUERY OK source="products" db=0.5ms idle=834.5ms UPDATE "products" AS p0 SET "views" = p0."views" + $1 WHERE (p0."id" = $2) RETURNING p0."views" [1, 1] ``` Good work! As we've seen, designing with contexts gives you a solid foundation to grow your application from. Using discrete, well-defined APIs that expose the intent of your system allows you to write more maintainable applications with reusable code. Now that we know how to start extending our context API, lets explore handling relationships within a context. ## [](contexts.html#in-context-relationships)In-context relationships Our basic catalog features are nice, but let's take it up a notch by categorizing products. Many ecommerce solutions allow products to be categorized in different ways, such as a product being marked for fashion, power tools, and so on. Starting with a one-to-one relationship between product and categories will cause major code changes later if we need to start supporting multiple categories. Let's set up a category association that will allow us to start off tracking a single category per product, but easily support more later as we grow our features. For now, categories will contain only textual information. Our first order of business is to decide where categories live in the application. We have our `Catalog` context, which manages the exhibition of our products. Product categorization is a natural fit here. Phoenix is also smart enough to generate code inside an existing context, which makes adding new resources to a context a breeze. Run the following command at your project root: > Sometimes it may be tricky to determine if two resources belong to the same context or not. In those cases, prefer distinct contexts per resource and refactor later if necessary. Otherwise you can easily end up with large contexts of loosely related entities. Also keep in mind that the fact two resources are related does not necessarily mean they belong to the same context, otherwise you would quickly end up with one large context, as the majority of resources in an application are connected to each other. To sum it up: if you are unsure, you should prefer separate modules (contexts). ``` $ mix phx.gen.context Catalog Category categories \ title:string:unique You are generating into an existing context. ... Would you like to proceed? [Yn] y * creating lib/hello/catalog/category.ex * creating priv/repo/migrations/20210203192325_create_categories.exs * injecting lib/hello/catalog.ex * injecting test/hello/catalog_test.exs * injecting test/support/fixtures/catalog_fixtures.ex Remember to update your repository by running migrations: $ mix ecto.migrate ``` This time around, we used [`mix phx.gen.context`](Mix.Tasks.Phx.Gen.Context.html), which is just like [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html), except it doesn't generate the web files for us. Since we already have controllers and templates for managing products, we can integrate the new category features into our existing web form and product show page. We can see we now have a new `Category` schema alongside our product schema at `lib/hello/catalog/category.ex`, and Phoenix told us it was *injecting* new functions in our existing Catalog context for the category functionality. The injected functions will look very familiar to our product functions, with new functions like `create_category`, `list_categories`, and so on. Before we migrate up, we need to do a second bit of code generation. Our category schema is great for representing an individual category in the system, but we need to support a many-to-many relationship between products and categories. Fortunately, ecto allows us to do this simply with a join table, so let's generate that now with the `ecto.gen.migration` command: ``` $ mix ecto.gen.migration create_product_categories * creating priv/repo/migrations/20210203192958_create_product_categories.exs ``` Next, let's open up the new migration file and add the following code to the `change` function: ``` defmodule Hello.Repo.Migrations.CreateProductCategories do use Ecto.Migration def change do create table(:product_categories, primary_key: false) do add :product_id, references(:products, on_delete: :delete_all) add :category_id, references(:categories, on_delete: :delete_all) end create index(:product_categories, [:product_id]) create unique_index(:product_categories, [:category_id, :product_id]) end end ``` We created a `product_categories` table and used the `primary_key: false` option since our join table does not need a primary key. Next we defined our `:product_id` and `:category_id` foreign key fields, and passed `on_delete: :delete_all` to ensure the database prunes our join table records if a linked product or category is deleted. By using a database constraint, we enforce data integrity at the database level, rather than relying on ad-hoc and error-prone application logic. Next, we created indexes for our foreign keys, one of which is a unique index to ensure a product cannot have duplicate categories. Note that we do not necessarily need single-column index for `category_id` because it is in the leftmost prefix of multicolumn index, which is enough for the database optimizer. Adding a redundant index, on the other hand, only adds overhead on write. With our migrations in place, we can migrate up. ``` $ mix ecto.migrate 18:20:36.489 [info] == Running 20210222231834 Hello.Repo.Migrations.CreateCategories.change/0 forward 18:20:36.493 [info] create table categories 18:20:36.508 [info] create index categories_title_index 18:20:36.512 [info] == Migrated 20210222231834 in 0.0s 18:20:36.547 [info] == Running 20210222231930 Hello.Repo.Migrations.CreateProductCategories.change/0 forward 18:20:36.547 [info] create table product_categories 18:20:36.557 [info] create index product_categories_product_id_index 18:20:36.560 [info] create index product_categories_category_id_product_id_index 18:20:36.562 [info] == Migrated 20210222231930 in 0.0s ``` Now that we have a `Catalog.Product` schema and a join table to associate products and categories, we're nearly ready to start wiring up our new features. Before we dive in, we first need real categories to select in our web UI. Let's quickly seed some new categories in the application. Add the following code to your seeds file in `priv/repo/seeds.exs`: ``` for title <- ["Home Improvement", "Power Tools", "Gardening", "Books", "Education"] do {:ok, _} = Hello.Catalog.create_category(%{title: title}) end ``` We simply enumerate over a list of category titles and use the generated `create_category/1` function of our catalog context to persist the new records. We can run the seeds with [`mix run`](../mix/Mix.Tasks.Run.html): ``` $ mix run priv/repo/seeds.exs [debug] QUERY OK db=3.1ms decode=1.1ms queue=0.7ms idle=2.2ms INSERT INTO "categories" ("title","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["Home Improvement", ~N[2021-02-03 19:39:53], ~N[2021-02-03 19:39:53]] [debug] QUERY OK db=1.2ms queue=1.3ms idle=12.3ms INSERT INTO "categories" ("title","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["Power Tools", ~N[2021-02-03 19:39:53], ~N[2021-02-03 19:39:53]] [debug] QUERY OK db=1.1ms queue=1.1ms idle=15.1ms INSERT INTO "categories" ("title","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["Gardening", ~N[2021-02-03 19:39:53], ~N[2021-02-03 19:39:53]] [debug] QUERY OK db=2.4ms queue=1.0ms idle=17.6ms INSERT INTO "categories" ("title","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["Books", ~N[2021-02-03 19:39:53], ~N[2021-02-03 19:39:53]] ``` Perfect. Before we integrate categories in the web layer, we need to let our context know how to associate products and categories. First, open up `lib/hello/catalog/product.ex` and add the following association: ``` + alias Hello.Catalog.Category schema "products" do field :description, :string field :price, :decimal field :title, :string field :views, :integer + many_to_many :categories, Category, join_through: "product_categories", on_replace: :delete timestamps() end ``` We used [`Ecto.Schema`](../ecto/3.10.1/Ecto.Schema.html)'s `many_to_many` macro to let Ecto know how to associate our product to multiple categories through the `"product_categories"` join table. We also used the `on_replace: :delete` option to declare that any existing join records should be deleted when we are changing our categories. With our schema associations set up, we can implement the selection of categories in our product form. To do so, we need to translate the user input of catalog IDs from the front-end to our many-to-many association. Fortunately Ecto makes this a breeze now that our schema is set up. Open up your catalog context and make the following changes: ``` + alias Hello.Catalog.Category - def get_product!(id), do: Repo.get!(Product, id) + def get_product!(id) do + Product |> Repo.get!(id) |> Repo.preload(:categories) + end def create_product(attrs \\ %{}) do %Product{} - |> Product.changeset(attrs) + |> change_product(attrs) |> Repo.insert() end def update_product(%Product{} = product, attrs) do product - |> Product.changeset(attrs) + |> change_product(attrs) |> Repo.update() end def change_product(%Product{} = product, attrs \\ %{}) do - Product.changeset(product, attrs) + categories = list_categories_by_id(attrs["category_ids"]) + product + |> Repo.preload(:categories) + |> Product.changeset(attrs) + |> Ecto.Changeset.put_assoc(:categories, categories) end + def list_categories_by_id(nil), do: [] + def list_categories_by_id(category_ids) do + Repo.all(from c in Category, where: c.id in ^category_ids) + end ``` First, we added `Repo.preload` to preload our categories when we fetch a product. This will allow us to reference `product.categories` in our controllers, templates, and anywhere else we want to make use of category information. Next, we modified our `create_product` and `update_product` functions to call into our existing `change_product` function to produce a changeset. Within `change_product` we added a lookup to find all categories if the `"category_ids"` attribute is present. Then we preloaded categories and called `Ecto.Changeset.put_assoc` to place the fetched categories into the changeset. Finally, we implemented the `list_categories_by_id/1` function to query the categories matching the category IDs, or return an empty list if no `"category_ids"` attribute is present. Now our `create_product` and `update_product` functions receive a changeset with the category associations all ready to go once we attempt an insert or update against our repo. Next, let's expose our new feature to the web by adding the category input to our product form. To keep our form template tidy, let's write a new function to wrap up the details of rendering a category select input for our product. Open up your `ProductHTML` view in `lib/hello_web/controllers/product_html.ex` and key this in: ``` def category_opts(changeset) do existing_ids = changeset |> Ecto.Changeset.get_change(:categories, []) |> Enum.map(& &1.data.id) for cat <- Hello.Catalog.list_categories(), do: [key: cat.title, value: cat.id, selected: cat.id in existing_ids] end ``` We added a new `category_opts/1` function which generates the select options for a multiple select tag we will add soon. We calculated the existing category IDs from our changeset, then used those values when we generate the select options for the input tag. We did this by enumerating over all of our categories and returning the appropriate `key`, `value`, and `selected` values. We marked an option as selected if the category ID was found in those category IDs in our changeset. With our `category_opts` function in place, we can open up `lib/hello_web/controllers/product_html/product_form.html.heex` and add: ``` ... <.input field={f[:views]} type="number" label="Views" /> + <.input field={f[:category_ids]} type="select" multiple={true} options={category_opts(@changeset)} /> <:actions> <.button>Save Product</.button> </:actions> ``` We added a `category_select` above our save button. Now let's try it out. Next, let's show the product's categories in the product show template. Add the following code to the list in `lib/hello_web/controllers/product_html/show.html.heex`: ``` <.list> ... + <:item title="Categories"> + <ul> + <li :for={cat <- @product.categories}>{cat.title}</li> + </ul> + </:item> </.list> ``` Now if we start the server with [`mix phx.server`](Mix.Tasks.Phx.Server.html) and visit [http://localhost:4000/products/new](http://localhost:4000/products/new), we'll see the new category multiple select input. Enter some valid product details, select a category or two, and click save. ``` Title: Elixir Flashcards Description: Flash card set for the Elixir programming language Price: 5.000000 Views: 0 Categories: Education Books ``` It's not much to look at yet, but it works! We added relationships within our context complete with data integrity enforced by the database. Not bad. Let's keep building! ## [](contexts.html#cross-context-dependencies)Cross-context dependencies Now that we have the beginnings of our product catalog features, let's begin to work on the other main features of our application – carting products from the catalog. In order to properly track products that have been added to a user's cart, we'll need a new place to persist this information, along with point-in-time product information like the price at time of carting. This is necessary so we can detect product price changes in the future. We know what we need to build, but now we need to decide where the cart functionality lives in our application. If we take a step back and think about the isolation of our application, the exhibition of products in our catalog distinctly differs from the responsibilities of managing a user's cart. A product catalog shouldn't care about the rules of our shopping cart system, and vice-versa. There's a clear need here for a separate context to handle the new cart responsibilities. Let's call it `ShoppingCart`. Let's create a `ShoppingCart` context to handle basic cart duties. Before we write code, let's imagine we have the following feature requirements: 1. Add products to a user's cart from the product show page 2. Store point-in-time product price information at time of carting 3. Store and update quantities in cart 4. Calculate and display sum of cart prices From the description, it's clear we need a `Cart` resource for storing the user's cart, along with a `CartItem` to track products in the cart. With our plan set, let's get to work. Run the following command to generate our new context: ``` $ mix phx.gen.context ShoppingCart Cart carts user_uuid:uuid:unique * creating lib/hello/shopping_cart/cart.ex * creating priv/repo/migrations/20210205203128_create_carts.exs * creating lib/hello/shopping_cart.ex * injecting lib/hello/shopping_cart.ex * creating test/hello/shopping_cart_test.exs * injecting test/hello/shopping_cart_test.exs * creating test/support/fixtures/shopping_cart_fixtures.ex * injecting test/support/fixtures/shopping_cart_fixtures.ex Some of the generated database columns are unique. Please provide unique implementations for the following fixture function(s) in test/support/fixtures/shopping_cart_fixtures.ex: def unique_cart_user_uuid do raise "implement the logic to generate a unique cart user_uuid" end Remember to update your repository by running migrations: $ mix ecto.migrate ``` We generated our new context `ShoppingCart`, with a new `ShoppingCart.Cart` schema to tie a user to their cart which holds cart items. We don't have real users yet, so for now our cart will be tracked by an anonymous user UUID that we'll add to our plug session in a moment. With our cart in place, let's generate our cart items: ``` $ mix phx.gen.context ShoppingCart CartItem cart_items \ cart_id:references:carts product_id:references:products \ price_when_carted:decimal quantity:integer You are generating into an existing context. ... Would you like to proceed? [Yn] y * creating lib/hello/shopping_cart/cart_item.ex * creating priv/repo/migrations/20210205213410_create_cart_items.exs * injecting lib/hello/shopping_cart.ex * injecting test/hello/shopping_cart_test.exs * injecting test/support/fixtures/shopping_cart_fixtures.ex Remember to update your repository by running migrations: $ mix ecto.migrate ``` We generated a new resource inside our `ShoppingCart` named `CartItem`. This schema and table will hold references to a cart and product, along with the price at the time we added the item to our cart, and the quantity the user wishes to purchase. Let's touch up the generated migration file in `priv/repo/migrations/*_create_cart_items.ex`: ``` create table(:cart_items) do - add :price_when_carted, :decimal + add :price_when_carted, :decimal, precision: 15, scale: 6, null: false add :quantity, :integer - add :cart_id, references(:carts, on_delete: :nothing) + add :cart_id, references(:carts, on_delete: :delete_all) - add :product_id, references(:products, on_delete: :nothing) + add :product_id, references(:products, on_delete: :delete_all) timestamps() end - create index(:cart_items, [:cart_id]) create index(:cart_items, [:product_id]) + create unique_index(:cart_items, [:cart_id, :product_id]) ``` We used the `:delete_all` strategy again to enforce data integrity. This way, when a cart or product is deleted from the application, we don't have to rely on application code in our `ShoppingCart` or `Catalog` contexts to worry about cleaning up the records. This keeps our application code decoupled and the data integrity enforcement where it belongs – in the database. We also added a unique constraint to ensure a duplicate product is not allowed to be added to a cart. As with the `product_categories` table, using a multi-column index lets us remove the separate index for the leftmost field (`cart_id`). With our database tables in place, we can now migrate up: ``` $ mix ecto.migrate 16:59:51.941 [info] == Running 20210205203342 Hello.Repo.Migrations.CreateCarts.change/0 forward 16:59:51.945 [info] create table carts 16:59:51.949 [info] create index carts_user_uuid_index 16:59:51.952 [info] == Migrated 20210205203342 in 0.0s 16:59:51.988 [info] == Running 20210205213410 Hello.Repo.Migrations.CreateCartItems.change/0 forward 16:59:51.988 [info] create table cart_items 16:59:51.998 [info] create index cart_items_cart_id_index 16:59:52.000 [info] create index cart_items_product_id_index 16:59:52.001 [info] create index cart_items_cart_id_product_id_index 16:59:52.002 [info] == Migrated 20210205213410 in 0.0s ``` Our database is ready to go with new `carts` and `cart_items` tables, but now we need to map that back into application code. You may be wondering how we can mix database foreign keys across different tables and how that relates to the context pattern of isolated, grouped functionality. Let's jump in and discuss the approaches and their tradeoffs. ### [](contexts.html#cross-context-data)Cross-context data So far, we've done a great job isolating the two main contexts of our application from each other, but now we have a necessary dependency to handle. Our `Catalog.Product` resource serves to keep the responsibilities of representing a product inside the catalog, but ultimately for an item to exist in the cart, a product from the catalog must be present. Given this, our `ShoppingCart` context will have a data dependency on the `Catalog` context. With that in mind, we have two options. One is to expose APIs on the `Catalog` context that allows us to efficiently fetch product data for use in the `ShoppingCart` system, which we would manually stitch together. Or we can use database joins to fetch the dependent data. Both are valid options given your tradeoffs and application size, but joining data from the database when you have a hard data dependency is just fine for a large class of applications and is the approach we will take here. Now that we know where our data dependencies exist, let's add our schema associations so we can tie shopping cart items to products. First, let's make a quick change to our cart schema in `lib/hello/shopping_cart/cart.ex` to associate a cart to its items: ``` schema "carts" do field :user_uuid, Ecto.UUID + has_many :items, Hello.ShoppingCart.CartItem timestamps() end ``` Now that our cart is associated to the items we place in it, let's set up the cart item associations inside `lib/hello/shopping_cart/cart_item.ex`: ``` schema "cart_items" do field :price_when_carted, :decimal field :quantity, :integer - field :cart_id, :id - field :product_id, :id + belongs_to :cart, Hello.ShoppingCart.Cart + belongs_to :product, Hello.Catalog.Product timestamps() end @doc false def changeset(cart_item, attrs) do cart_item |> cast(attrs, [:price_when_carted, :quantity]) |> validate_required([:price_when_carted, :quantity]) + |> validate_number(:quantity, greater_than_or_equal_to: 0, less_than: 100) end ``` First, we replaced the `cart_id` field with a standard `belongs_to` pointing at our `ShoppingCart.Cart` schema. Next, we replaced our `product_id` field by adding our first cross-context data dependency with a `belongs_to` for the `Catalog.Product` schema. Here, we intentionally coupled the data boundaries because it provides exactly what we need: an isolated context API with the bare minimum knowledge necessary to reference a product in our system. Next, we added a new validation to our changeset. With `validate_number/3`, we ensure any quantity provided by user input is between 0 and 100. With our schemas in place, we can start integrating the new data structures and `ShoppingCart` context APIs into our web-facing features. ### [](contexts.html#adding-shopping-cart-functions)Adding Shopping Cart functions As we mentioned before, the context generators are only a starting point for our application. We can and should write well-named, purpose built functions to accomplish the goals of our context. We have a few new features to implement. First, we need to ensure every user of our application is granted a cart if one does not yet exist. From there, we can then allow users to add items to their cart, update item quantities, and calculate cart totals. Let's get started! We won't focus on a real user authentication system at this point, but by the time we're done, you'll be able to naturally integrate one with what we've written here. To simulate a current user session, open up your `lib/hello_web/router.ex` and key this in: ``` pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.LayoutView, :root} plug :protect_from_forgery plug :put_secure_browser_headers + plug :fetch_current_user + plug :fetch_current_cart end + defp fetch_current_user(conn, _) do + if user_uuid = get_session(conn, :current_uuid) do + assign(conn, :current_uuid, user_uuid) + else + new_uuid = Ecto.UUID.generate() + + conn + |> assign(:current_uuid, new_uuid) + |> put_session(:current_uuid, new_uuid) + end + end + alias Hello.ShoppingCart + + defp fetch_current_cart(conn, _opts) do + if cart = ShoppingCart.get_cart_by_user_uuid(conn.assigns.current_uuid) do + assign(conn, :cart, cart) + else + {:ok, new_cart} = ShoppingCart.create_cart(conn.assigns.current_uuid) + assign(conn, :cart, new_cart) + end + end ``` We added a new `:fetch_current_user` and `:fetch_current_cart` plug to our browser pipeline to run on all browser-based requests. Next, we implemented the `fetch_current_user` plug which simply checks the session for a user UUID that was previously added. If we find one, we add a `current_uuid` assign to the connection and we're done. In the case we haven't yet identified this visitor, we generate a unique UUID with `Ecto.UUID.generate()`, then we place that value in the `current_uuid` assign, along with a new session value to identify this visitor on future requests. A random, unique ID isn't much to represent a user, but it's enough for us to track and identify a visitor across requests, which is all we need for now. Later as our application becomes more complete, you'll be ready to migrate to a complete user authentication solution. With a guaranteed current user, we then implemented the `fetch_current_cart` plug which either finds a cart for the user UUID or creates a cart for the current user and assigns the result in the connection assigns. We'll need to implement our `ShoppingCart.get_cart_by_user_uuid/1` and modify the create cart function to accept a UUID, but let's add our routes first. We'll need to implement a cart controller for handling cart operations like viewing a cart, updating quantities, and initiating the checkout process, as well as a cart items controller for adding and removing individual items to and from the cart. Add the following routes to your router in `lib/hello_web/router.ex`: ``` scope "/", HelloWeb do pipe_through :browser get "/", PageController, :index resources "/products", ProductController + resources "/cart_items", CartItemController, only: [:create, :delete] + get "/cart", CartController, :show + put "/cart", CartController, :update end ``` We added a `resources` declaration for a `CartItemController`, which will wire up the routes for a create and delete action for adding and removing individual cart items. Next, we added two new routes pointing at a `CartController`. The first route, a GET request, will map to our show action, to show the cart contents. The second route, a PUT request, will handle the submission of a form for updating our cart quantities. With our routes in place, let's add the ability to add an item to our cart from the product show page. Create a new file at `lib/hello_web/controllers/cart_item_controller.ex` and key this in: ``` defmodule HelloWeb.CartItemController do use HelloWeb, :controller alias Hello.ShoppingCart def create(conn, %{"product_id" => product_id}) do case ShoppingCart.add_item_to_cart(conn.assigns.cart, product_id) do {:ok, _item} -> conn |> put_flash(:info, "Item added to your cart") |> redirect(to: ~p"/cart") {:error, _changeset} -> conn |> put_flash(:error, "There was an error adding the item to your cart") |> redirect(to: ~p"/cart") end end def delete(conn, %{"id" => product_id}) do {:ok, _cart} = ShoppingCart.remove_item_from_cart(conn.assigns.cart, product_id) redirect(conn, to: ~p"/cart") end end ``` We defined a new `CartItemController` with the create and delete actions that we declared in our router. For `create`, we call a `ShoppingCart.add_item_to_cart/2` function which we'll implement in a moment. If successful, we show a flash successful message and redirect to the cart show page; else, we show a flash error message and redirect to the cart show page. For `delete`, we'll call a `remove_item_from_cart` function which we'll implement on our `ShoppingCart` context and then redirect back to the cart show page. We haven't implemented these two shopping cart functions yet, but notice how their names scream their intent: `add_item_to_cart` and `remove_item_from_cart` make it obvious what we are accomplishing here. It also allows us to spec out our web layer and context APIs without thinking about all the implementation details at once. Let's implement the new interface for the `ShoppingCart` context API in `lib/hello/shopping_cart.ex`: ``` + alias Hello.Catalog - alias Hello.ShoppingCart.Cart + alias Hello.ShoppingCart.{Cart, CartItem} + def get_cart_by_user_uuid(user_uuid) do + Repo.one( + from(c in Cart, + where: c.user_uuid == ^user_uuid, + left_join: i in assoc(c, :items), + left_join: p in assoc(i, :product), + order_by: [asc: i.inserted_at], + preload: [items: {i, product: p}] + ) + ) + end - def create_cart(attrs \\ %{}) do - %Cart{} - |> Cart.changeset(attrs) + def create_cart(user_uuid) do + %Cart{user_uuid: user_uuid} + |> Cart.changeset(%{}) |> Repo.insert() + |> case do + {:ok, cart} -> {:ok, reload_cart(cart)} + {:error, changeset} -> {:error, changeset} + end end + defp reload_cart(%Cart{} = cart), do: get_cart_by_user_uuid(cart.user_uuid) + + def add_item_to_cart(%Cart{} = cart, product_id) do + product = Catalog.get_product!(product_id) + + %CartItem{quantity: 1, price_when_carted: product.price} + |> CartItem.changeset(%{}) + |> Ecto.Changeset.put_assoc(:cart, cart) + |> Ecto.Changeset.put_assoc(:product, product) + |> Repo.insert( + on_conflict: [inc: [quantity: 1]], + conflict_target: [:cart_id, :product_id] + ) + end + + def remove_item_from_cart(%Cart{} = cart, product_id) do + {1, _} = + Repo.delete_all( + from(i in CartItem, + where: i.cart_id == ^cart.id, + where: i.product_id == ^product_id + ) + ) + + {:ok, reload_cart(cart)} + end ``` We started by implementing `get_cart_by_user_uuid/1` which fetches our cart and joins the cart items, and their products so that we have the full cart populated with all preloaded data. Next, we modified our `create_cart` function to accept a user UUID instead of attributes, which we used to populate the `user_uuid` field. If the insert is successful, we reload the cart contents by calling a private `reload_cart/1` function, which simply calls `get_cart_by_user_uuid/1` to refetch data. Next, we wrote our new `add_item_to_cart/2` function which accepts a cart struct and a product id. We proceed to fetch the product with `Catalog.get_product!/1`, showing how contexts can naturally invoke other contexts if required. You could also have chosen to receive the product as argument and you would achieve similar results. Then we used an upsert operation against our repo to either insert a new cart item into the database, or increase the quantity by one if it already exists in the cart. This is accomplished via the `on_conflict` and `conflict_target` options, which tells our repo how to handle an insert conflict. Finally, we implemented `remove_item_from_cart/2` where we simply issue a `Repo.delete_all` call with a query to delete the cart item in our cart that matches the product ID. Finally, we reload the cart contents by calling `reload_cart/1`. With our new cart functions in place, we can now expose the "Add to cart" button on the product catalog show page. Open up your template in `lib/hello_web/controllers/product_html/show.html.heex` and make the following changes: ``` ... <.link href={~p"/products/#{@product}/edit"}> <.button>Edit product</.button> </.link> + <.link href={~p"/cart_items?product_id=#{@product.id}"} method="post"> + <.button>Add to cart</.button> + </.link> ... ``` The `link` function component from `Phoenix.Component` accepts a `:method` attribute to issue an HTTP verb when clicked, instead of the default GET request. With this link in place, the "Add to cart" link will issue a POST request, which will be matched by the route we defined in router which dispatches to the `CartItemController.create/2` function. Let's try it out. Start your server with [`mix phx.server`](Mix.Tasks.Phx.Server.html) and visit a product page. If we try clicking the add to cart link, we'll be greeted by an error page with the following logs in the console: ``` [info] POST /cart_items [debug] Processing with HelloWeb.CartItemController.create/2 Parameters: %{"_method" => "post", "product_id" => "1", ...} Pipelines: [:browser] INSERT INTO "cart_items" ... [info] Sent 302 in 24ms [info] GET /cart [debug] Processing with HelloWeb.CartController.show/2 Parameters: %{} Pipelines: [:browser] [debug] QUERY OK source="carts" db=1.9ms idle=1798.5ms [error] #PID<0.856.0> running HelloWeb.Endpoint (connection #PID<0.841.0>, stream id 5) terminated Server: localhost:4000 (http) Request: GET /cart ** (exit) an exception was raised: ** (UndefinedFunctionError) function HelloWeb.CartController.init/1 is undefined (module HelloWeb.CartController is not available) ... ``` It's working! Kind of. If we follow the logs, we see our POST to the `/cart_items` path. Next, we can see our `ShoppingCart.add_item_to_cart` function successfully inserted a row into the `cart_items` table, and then we issued a redirect to `/cart`. Before our error, we also see a query to the `carts` table, which means we're fetching the current user's cart. So far so good. We know our `CartItem` controller and new `ShoppingCart` context functions are doing their jobs, but we've hit our next unimplemented feature when the router attempts to dispatch to a nonexistent cart controller. Let's create the cart controller, view, and template to display and manage user carts. Create a new file at `lib/hello_web/controllers/cart_controller.ex` and key this in: ``` defmodule HelloWeb.CartController do use HelloWeb, :controller alias Hello.ShoppingCart def show(conn, _params) do render(conn, :show, changeset: ShoppingCart.change_cart(conn.assigns.cart)) end end ``` We defined a new cart controller to handle the `get "/cart"` route. For showing a cart, we render a `"show.html"` template which we'll create in a moment. We know we need to allow the cart items to be changed by quantity updates, so right away we know we'll need a cart changeset. Fortunately, the context generator included a `ShoppingCart.change_cart/1` function, which we'll use. We pass it our cart struct which is already in the connection assigns thanks to the `fetch_current_cart` plug we defined in the router. Next, we can implement the view and template. Create a new view file at `lib/hello_web/controllers/cart_html.ex` with the following content: ``` defmodule HelloWeb.CartHTML do use HelloWeb, :html alias Hello.ShoppingCart embed_templates "cart_html/*" def currency_to_str(%Decimal{} = val), do: "$#{Decimal.round(val, 2)}" end ``` We created a view to render our `show.html` template and aliased our `ShoppingCart` context so it will be in scope for our template. We'll need to display the cart prices like product item price, cart total, etc, so we defined a `currency_to_str/1` which takes our decimal struct, rounds it properly for display, and prepends a USD dollar sign. Next we can create the template at `lib/hello_web/controllers/cart_html/show.html.heex`: ``` <.header> My Cart <:subtitle :if={@cart.items == []}>Your cart is empty</:subtitle> </.header> <div :if={@cart.items !== []}> <.simple_form :let={f} for={@changeset} action={~p"/cart"}> <.inputs_for :let={%{data: item} = item_form} field={f[:items]}> <.input field={item_form[:quantity]} type="number" label={item.product.title} /> {currency_to_str(ShoppingCart.total_item_price(item))} </.inputs_for> <:actions> <.button>Update cart</.button> </:actions> </.simple_form> <b>Total</b>: {currency_to_str(ShoppingCart.total_cart_price(@cart))} </div> <.back navigate={~p"/products"}>Back to products</.back> ``` We started by showing the empty cart message if our preloaded `cart.items` is empty. If we have items, we use the `simple_form` component provided by our `HelloWeb.CoreComponents` to take our cart changeset that we assigned in the `CartController.show/2` action and create a form which maps to our cart controller `update/2` action. Within the form, we use the `inputs_for` component to render inputs for the nested cart items. This will allow us to map item inputs back together when the form is submitted. Next, we display a number input for the item quantity and label it with the product title. We finish the item form by converting the item price to string. We haven't written the `ShoppingCart.total_item_price/1` function yet, but again we employed the idea of clear, descriptive public interfaces for our contexts. After rendering inputs for all the cart items, we show an "update cart" submit button, along with the total price of the entire cart. This is accomplished with another new `ShoppingCart.total_cart_price/1` function which we'll implement in a moment. Finally, we added a `back` component to go back to our products page. We're almost ready to try out our cart page, but first we need to implement our new currency calculation functions. Open up your shopping cart context at `lib/hello/shopping_cart.ex` and add these new functions: ``` def total_item_price(%CartItem{} = item) do Decimal.mult(item.product.price, item.quantity) end def total_cart_price(%Cart{} = cart) do Enum.reduce(cart.items, 0, fn item, acc -> item |> total_item_price() |> Decimal.add(acc) end) end ``` We implemented `total_item_price/1` which accepts a `%CartItem{}` struct. To calculate the total price, we simply take the preloaded product's price and multiply it by the item's quantity. We used [`Decimal.mult/2`](../decimal/2.1.1/Decimal.html#mult/2) to take our decimal currency struct and multiply it with proper precision. Similarly for calculating the total cart price, we implemented a `total_cart_price/1` function which accepts the cart and sums the preloaded product prices for items in the cart. We again make use of the [`Decimal`](../decimal/2.1.1/Decimal.html) functions to add our decimal structs together. Now that we can calculate price totals, let's try it out! Visit [`http://localhost:4000/cart`](http://localhost:4000/cart) and you should already see your first item in the cart. Going back to the same product and clicking "add to cart" will show our upsert in action. Your quantity should now be two. Nice work! Our cart page is almost complete, but submitting the form will yield yet another error. ``` Request: POST /cart ** (exit) an exception was raised: ** (UndefinedFunctionError) function HelloWeb.CartController.update/2 is undefined or private ``` Let's head back to our `CartController` at `lib/hello_web/controllers/cart_controller.ex` and implement the update action: ``` def update(conn, %{"cart" => cart_params}) do case ShoppingCart.update_cart(conn.assigns.cart, cart_params) do {:ok, _cart} -> redirect(conn, to: ~p"/cart") {:error, _changeset} -> conn |> put_flash(:error, "There was an error updating your cart") |> redirect(to: ~p"/cart") end end ``` We started by plucking out the cart params from the form submit. Next, we call our existing `ShoppingCart.update_cart/2` function which was added by the context generator. We'll need to make some changes to this function, but the interface is good as is. If the update is successful, we redirect back to the cart page, otherwise we show a flash error message and send the user back to the cart page to fix any mistakes. Out-of-the-box, our `ShoppingCart.update_cart/2` function only concerned itself with casting the cart params into a changeset and updates it against our repo. For our purposes, we now need it to handle nested cart item associations, and most importantly, business logic for how to handle quantity updates like zero-quantity items being removed from the cart. Head back over to your shopping cart context in `lib/hello/shopping_cart.ex` and replace your `update_cart/2` function with the following implementation: ``` def update_cart(%Cart{} = cart, attrs) do changeset = cart |> Cart.changeset(attrs) |> Ecto.Changeset.cast_assoc(:items, with: &CartItem.changeset/2) Ecto.Multi.new() |> Ecto.Multi.update(:cart, changeset) |> Ecto.Multi.delete_all(:discarded_items, fn %{cart: cart} -> from(i in CartItem, where: i.cart_id == ^cart.id and i.quantity == 0) end) |> Repo.transaction() |> case do {:ok, %{cart: cart}} -> {:ok, cart} {:error, :cart, changeset, _changes_so_far} -> {:error, changeset} end end ``` We started much like how our out-of-the-box code started – we take the cart struct and cast the user input to a cart changeset, except this time we use [`Ecto.Changeset.cast_assoc/3`](../ecto/3.10.1/Ecto.Changeset.html#cast_assoc/3) to cast the nested item data into `CartItem` changesets. Remember the `<.inputs_for />` call in our cart form template? That hidden ID data is what allows Ecto's `cast_assoc` to map item data back to existing item associations in the cart. Next we use [`Ecto.Multi.new/0`](../ecto/3.10.1/Ecto.Multi.html#new/0), which you may not have seen before. Ecto's `Multi` is a feature that allows lazily defining a chain of named operations to eventually execute inside a database transaction. Each operation in the multi chain receives the values from the previous steps and executes until a failed step is encountered. When an operation fails, the transaction is rolled back and an error is returned, otherwise the transaction is committed. For our multi operations, we start by issuing an update of our cart, which we named `:cart`. After the cart update is issued, we perform a multi `delete_all` operation, which takes the updated cart and applies our zero-quantity logic. We prune any items in the cart with zero quantity by returning an ecto query that finds all cart items for this cart with an empty quantity. Calling `Repo.transaction/1` with our multi will execute the operations in a new transaction and we return the success or failure result to the caller just like the original function. Let's head back to the browser and try it out. Add a few products to your cart, update the quantities, and watch the values changes along with the price calculations. Setting any quantity to 0 will also remove the item. Pretty neat! ## [](contexts.html#adding-an-orders-context)Adding an Orders context With our `Catalog` and `ShoppingCart` contexts, we're seeing first-hand how our well-considered modules and function names are yielding clear and maintainable code. Our last order of business is to allow the user to initiate the checkout process. We won't go as far as integrating payment processing or order fulfillment, but we'll get you started in that direction. Like before, we need to decide where code for completing an order should live. Is it part of the catalog? Clearly not, but what about the shopping cart? Shopping carts are related to orders – after all, the user has to add items in order to purchase any products – but should the order checkout process be grouped here? If we stop and consider the order process, we'll see that orders involve related, but distinctly different data from the cart contents. Also, business rules around the checkout process are much different than carting. For example, we may allow a user to add a back-ordered item to their cart, but we could not allow an order with no inventory to be completed. Additionally, we need to capture point-in-time product information when an order is completed, such as the price of the items *at payment transaction time*. This is essential because a product price may change in the future, but the line items in our order must always record and display what we charged at time of purchase. For these reasons, we can start to see that ordering can stand on its own with its own data concerns and business rules. Naming wise, `Orders` clearly defines the scope of our context, so let's get started by again taking advantage of the context generators. Run the following command in your console: ``` $ mix phx.gen.context Orders Order orders user_uuid:uuid total_price:decimal * creating lib/hello/orders/order.ex * creating priv/repo/migrations/20210209214612_create_orders.exs * creating lib/hello/orders.ex * injecting lib/hello/orders.ex * creating test/hello/orders_test.exs * injecting test/hello/orders_test.exs * creating test/support/fixtures/orders_fixtures.ex * injecting test/support/fixtures/orders_fixtures.ex Remember to update your repository by running migrations: $ mix ecto.migrate ``` We generated an `Orders` context. We added a `user_uuid` field to associate our placeholder current user to an order, along with a `total_price` column. With our starting point in place, let's open up the newly created migration in `priv/repo/migrations/*_create_orders.exs` and make the following changes: ``` def change do create table(:orders) do add :user_uuid, :uuid - add :total_price, :decimal + add :total_price, :decimal, precision: 15, scale: 6, null: false timestamps() end end ``` Like we did previously, we gave appropriate precision and scale options for our decimal column which will allow us to store currency without precision loss. We also added a not-null constraint to enforce all orders to have a price. The orders table alone doesn't hold much information, but we know we'll need to store point-in-time product price information of all the items in the order. For that, we'll add an additional struct for this context named `LineItem`. Line items will capture the price of the product *at payment transaction time*. Please run the following command: ``` $ mix phx.gen.context Orders LineItem order_line_items \ price:decimal quantity:integer \ order_id:references:orders product_id:references:products You are generating into an existing context. ... Would you like to proceed? [Yn] y * creating lib/hello/orders/line_item.ex * creating priv/repo/migrations/20210209215050_create_order_line_items.exs * injecting lib/hello/orders.ex * injecting test/hello/orders_test.exs * injecting test/support/fixtures/orders_fixtures.ex Remember to update your repository by running migrations: $ mix ecto.migrate ``` We used the `phx.gen.context` command to generate the `LineItem` Ecto schema and inject supporting functions into our orders context. Like before, let's modify the migration in `priv/repo/migrations/*_create_order_line_items.exs` and make the following decimal field changes: ``` def change do create table(:order_line_items) do - add :price, :decimal + add :price, :decimal, precision: 15, scale: 6, null: false add :quantity, :integer add :order_id, references(:orders, on_delete: :nothing) add :product_id, references(:products, on_delete: :nothing) timestamps() end create index(:order_line_items, [:order_id]) create index(:order_line_items, [:product_id]) end ``` With our migration in place, let's wire up our orders and line items associations in `lib/hello/orders/order.ex`: ``` schema "orders" do field :total_price, :decimal field :user_uuid, Ecto.UUID + has_many :line_items, Hello.Orders.LineItem + has_many :products, through: [:line_items, :product] timestamps() end ``` We used `has_many :line_items` to associate orders and line items, just like we've seen before. Next, we used the `:through` feature of `has_many`, which allows us to instruct ecto how to associate resources across another relationship. In this case, we can associate products of an order by finding all products through associated line items. Next, let's wire up the association in the other direction in `lib/hello/orders/line_item.ex`: ``` schema "order_line_items" do field :price, :decimal field :quantity, :integer - field :order_id, :id - field :product_id, :id + belongs_to :order, Hello.Orders.Order + belongs_to :product, Hello.Catalog.Product timestamps() end ``` We used `belongs_to` to associate line items to orders and products. With our associations in place, we can start integrating the web interface into our order process. Open up your router `lib/hello_web/router.ex` and add the following line: ``` scope "/", HelloWeb do pipe_through :browser ... + resources "/orders", OrderController, only: [:create, :show] end ``` We wired up `create` and `show` routes for our generated `OrderController`, since these are the only actions we need at the moment. With our routes in place, we can now migrate up: ``` $ mix ecto.migrate 17:14:37.715 [info] == Running 20210209214612 Hello.Repo.Migrations.CreateOrders.change/0 forward 17:14:37.720 [info] create table orders 17:14:37.755 [info] == Migrated 20210209214612 in 0.0s 17:14:37.784 [info] == Running 20210209215050 Hello.Repo.Migrations.CreateOrderLineItems.change/0 forward 17:14:37.785 [info] create table order_line_items 17:14:37.795 [info] create index order_line_items_order_id_index 17:14:37.796 [info] create index order_line_items_product_id_index 17:14:37.798 [info] == Migrated 20210209215050 in 0.0s ``` Before we render information about our orders, we need to ensure our order data is fully populated and can be looked up by a current user. Open up your orders context in `lib/hello/orders.ex` and replace your `get_order!/1` function by a new `get_order!/2` definition: ``` def get_order!(user_uuid, id) do Order |> Repo.get_by!(id: id, user_uuid: user_uuid) |> Repo.preload([line_items: [:product]]) end ``` We rewrote the function to accept a user UUID and query our repo for an order matching the user's ID for a given order ID. Then we populated the order by preloading our line item and product associations. To complete an order, our cart page can issue a POST to the `OrderController.create` action, but we need to implement the operations and logic to actually complete an order. Like before, we'll start at the web interface. Create a new file at `lib/hello_web/controllers/order_controller.ex` and key this in: ``` defmodule HelloWeb.OrderController do use HelloWeb, :controller alias Hello.Orders def create(conn, _) do case Orders.complete_order(conn.assigns.cart) do {:ok, order} -> conn |> put_flash(:info, "Order created successfully.") |> redirect(to: ~p"/orders/#{order}") {:error, _reason} -> conn |> put_flash(:error, "There was an error processing your order") |> redirect(to: ~p"/cart") end end end ``` We wrote the `create` action to call an as-yet-implemented `Orders.complete_order/1` function. Our code is technically "creating" an order, but it's important to step back and consider the naming of your interfaces. The act of *completing* an order is extremely important in our system. Money changes hands in a transaction, physical goods could be automatically shipped, etc. Such an operation deserves a better, more obvious function name, such as `complete_order`. If the order is completed successfully we redirect to the show page, otherwise a flash error is shown as we redirect back to the cart page. Here is also a good opportunity to highlight that contexts can naturally work with data defined by other contexts too. This will be especially common with data that is used throughout the application, such as the cart here (but it can also be the current user or the current project, and so forth, depending on your project). Now we can implement our `Orders.complete_order/1` function. To complete an order, our job will require a few operations: 1. A new order record must be persisted with the total price of the order 2. All items in the cart must be transformed into new order line items records with quantity and point-in-time product price information 3. After successful order insert (and eventual payment), items must be pruned from the cart From our requirements alone, we can start to see why a generic `create_order` function doesn't cut it. Let's implement this new function in `lib/hello/orders.ex`: ``` alias Hello.Orders.LineItem alias Hello.ShoppingCart def complete_order(%ShoppingCart.Cart{} = cart) do line_items = Enum.map(cart.items, fn item -> %{product_id: item.product_id, price: item.product.price, quantity: item.quantity} end) order = Ecto.Changeset.change(%Order{}, user_uuid: cart.user_uuid, total_price: ShoppingCart.total_cart_price(cart), line_items: line_items ) Ecto.Multi.new() |> Ecto.Multi.insert(:order, order) |> Ecto.Multi.run(:prune_cart, fn _repo, _changes -> ShoppingCart.prune_cart_items(cart) end) |> Repo.transaction() |> case do {:ok, %{order: order}} -> {:ok, order} {:error, name, value, _changes_so_far} -> {:error, {name, value}} end end ``` We started by mapping the `%ShoppingCart.CartItem{}`'s in our shopping cart into a map of order line items structs. The job of the order line item record is to capture the price of the product *at payment transaction time*, so we reference the product's price here. Next, we create a bare order changeset with [`Ecto.Changeset.change/2`](../ecto/3.10.1/Ecto.Changeset.html#change/2) and associate our user UUID, set our total price calculation, and place our order line items in the changeset. With a fresh order changeset ready to be inserted, we can again make use of [`Ecto.Multi`](../ecto/3.10.1/Ecto.Multi.html) to execute our operations in a database transaction. We start by inserting the order, followed by a `run` operation. The [`Ecto.Multi.run/3`](../ecto/3.10.1/Ecto.Multi.html#run/3) function allows us to run any code in the function which must either succeed with `{:ok, result}` or error, which halts and rolls back the transaction. Here, we simply call into our shopping cart context and ask it to prune all items in a cart. Running the transaction will execute the multi as before and we return the result to the caller. To close out our order completion, we need to implement the `ShoppingCart.prune_cart_items/1` function in `lib/hello/shopping_cart.ex`: ``` def prune_cart_items(%Cart{} = cart) do {_, _} = Repo.delete_all(from(i in CartItem, where: i.cart_id == ^cart.id)) {:ok, reload_cart(cart)} end ``` Our new function accepts the cart struct and issues a `Repo.delete_all` which accepts a query of all items for the provided cart. We return a success result by simply reloading the pruned cart to the caller. With our context complete, we now need to show the user their completed order. Head back to your order controller and add the `show/2` action: ``` def show(conn, %{"id" => id}) do order = Orders.get_order!(conn.assigns.current_uuid, id) render(conn, :show, order: order) end ``` We added the show action to pass our `conn.assigns.current_uuid` to `get_order!` which authorizes orders to be viewable only by the owner of the order. Next, we can implement the view and template. Create a new view file at `lib/hello_web/controllers/order_html.ex` with the following content: ``` defmodule HelloWeb.OrderHTML do use HelloWeb, :html embed_templates "order_html/*" end ``` Next we can create the template at `lib/hello_web/controllers/order_html/show.html.heex`: ``` <.header> Thank you for your order! <:subtitle> <strong>User uuid: </strong>{@order.user_uuid} </:subtitle> </.header> <.table id="items" rows={@order.line_items}> <:col :let={item} label="Title">{item.product.title}</:col> <:col :let={item} label="Quantity">{item.quantity}</:col> <:col :let={item} label="Price"> {HelloWeb.CartHTML.currency_to_str(item.price)} </:col> </.table> <strong>Total price:</strong> {HelloWeb.CartHTML.currency_to_str(@order.total_price)} <.back navigate={~p"/products"}>Back to products</.back> ``` To show our completed order, we displayed the order's user, followed by the line item listing with product title, quantity, and the price we "transacted" when completing the order, along with the total price. Our last addition will be to add the "complete order" button to our cart page to allow completing an order. Add the following button to the &lt;.header&gt; of the cart show template in `lib/hello_web/controllers/cart_html/show.html.heex`: ``` <.header> My Cart + <:actions> + <.link href={~p"/orders"} method="post"> + <.button>Complete order</.button> + </.link> + </:actions> </.header> ``` We added a link with `method="post"` to send a POST request to our `OrderController.create` action. If we head back to our cart page at [`http://localhost:4000/cart`](http://localhost:4000/cart) and complete an order, we'll be greeted by our rendered template: ``` Thank you for your order! User uuid: 08964c7c-908c-4a55-bcd3-9811ad8b0b9d Title Quantity Price Metaprogramming Elixir 2 $15.00 Total price: $30.00 ``` Nice work! We haven't added payments, but we can already see how our `ShoppingCart` and `Orders` context splitting is driving us towards a maintainable solution. With our cart items separated from our order line items, we are well equipped in the future to add payment transactions, cart price detection, and more. Great work! ## [](contexts.html#faq)FAQ ### [](contexts.html#when-to-use-code-generators)When to use code generators? In this guide, we have used code generators for schemas, contexts, controllers, and more. If you are happy to move forward with Phoenix defaults, feel free to rely on generators to scaffold large parts of your application. When using Phoenix generators, the main question you need to answer is: does this new functionality (with its schema, table, and fields) belong to one of the existing contexts or a new one? This way, Phoenix generators guide you to use contexts to group related functionality, instead of having several dozens of schemas laying around without any structure. And remember: if you're stuck when trying to come up with a context name, you can simply use the plural form of the resource you're creating. ### [](contexts.html#how-do-i-structure-code-inside-contexts)How do I structure code inside contexts? You may wonder how to organize the code inside contexts. For example, should you define a module for changesets (such as ProductChangesets) and another module for queries (such as ProductQueries)? One important benefit of contexts is that this decision does not matter much. The context is your public API, the other modules are private. Contexts isolate these modules into small groups so the surface area of your application is the context and not *all of your code*. So while you and your team could establish patterns for organizing these private modules, it is also our opinion it is completely fine for them to be different. The major focus should be on how the contexts are defined and how they interact with each other (and with your web application). Think about it as a well-kept neighbourhood. Your contexts are houses, you want to keep them well-preserved, well-connected, etc. Inside the houses, they may all be a little bit different, and that's fine. ### [](contexts.html#returning-ecto-structures-from-context-apis)Returning Ecto structures from context APIs As we explored the context API, you might have wondered: > If one of the goals of our context is to encapsulate Ecto Repo access, why does `create_user/1` return an [`Ecto.Changeset`](../ecto/3.10.1/Ecto.Changeset.html) struct when we fail to create a user? Although Changesets are part of Ecto, they are not tied to the database, and they can be used to map data from and to any source, which makes it a general and useful data structure for tracking field changes, perform validations, and generate error messages. For those reasons, `%Ecto.Changeset{}` is a good choice to model the data changes between your contexts and your web layer - regardless if you are talking to an API or the database. Finally, note that your controllers and views are not hardcoded to work exclusively with Ecto either. Instead, Phoenix defines protocols such as [`Phoenix.Param`](Phoenix.Param.html) and [`Phoenix.HTML.FormData`](../phoenix_html/4.0.0/Phoenix.HTML.FormData.html), which allow any library to extend how Phoenix generates URL parameters or renders forms. Conveniently for us, the `phoenix_ecto` project implements those protocols, but you could as well bring your own data structures and implement them yourself. [← Previous Page Ecto](ecto.html) [Next Page → JSON and APIs](json_and_apis.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/contexts.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/controllers.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/controllers.md#L1 "View Source") Controllers > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [request life-cycle guide](request_lifecycle.html). Phoenix controllers act as intermediary modules. Their functions — called actions — are invoked from the router in response to HTTP requests. The actions, in turn, gather all the necessary data and perform all the necessary steps before invoking the view layer to render a template or returning a JSON response. Phoenix controllers also build on the Plug package, and are themselves plugs. Controllers provide the functions to do almost anything we need to in an action. If we do find ourselves looking for something that Phoenix controllers don't provide, we might find what we're looking for in Plug itself. Please see the [Plug guide](plug.html) or the [Plug documentation](../plug/1.15.3/Plug.html) for more information. A newly generated Phoenix app will have a single controller named `PageController`, which can be found at `lib/hello_web/controllers/page_controller.ex` which looks like this: ``` defmodule HelloWeb.PageController do use HelloWeb, :controller def home(conn, _params) do render(conn, :home, layout: false) end end ``` The first line below the module definition invokes the `__using__/1` macro of the `HelloWeb` module, which imports some useful modules. `PageController` gives us the `home` action to display the Phoenix [welcome page](http://localhost:4000/) associated with the default route Phoenix defines in the router. ## [](controllers.html#actions)Actions Controller actions are just functions. We can name them anything we like as long as they follow Elixir's naming rules. The only requirement we must fulfill is that the action name matches a route defined in the router. For example, in `lib/hello_web/router.ex` we could change the action name in the default route that Phoenix gives us in a new app from `home`: ``` get "/", PageController, :home ``` to `index`: ``` get "/", PageController, :index ``` as long as we change the action name in `PageController` to `index` as well, the [welcome page](http://localhost:4000/) will load as before. ``` defmodule HelloWeb.PageController do ... def index(conn, _params) do render(conn, :index) end end ``` While we can name our actions whatever we like, there are conventions for action names which we should follow whenever possible. We went over these in the [routing guide](routing.html), but we'll take another quick look here. - index - renders a list of all items of the given resource type - show - renders an individual item by ID - new - renders a form for creating a new item - create - receives parameters for one new item and saves it in a data store - edit - retrieves an individual item by ID and displays it in a form for editing - update - receives parameters for one edited item and saves the item to a data store - delete - receives an ID for an item to be deleted and deletes it from a data store Each of these actions takes two parameters, which will be provided by Phoenix behind the scenes. The first parameter is always `conn`, a struct which holds information about the request such as the host, path elements, port, query string, and much more. `conn` comes to Phoenix via Elixir's Plug middleware framework. More detailed information about `conn` can be found in the [Plug.Conn documentation](../plug/1.15.3/Plug.Conn.html). The second parameter is `params`. Not surprisingly, this is a map which holds any parameters passed along in the HTTP request. It is a good practice to pattern match against parameters in the function signature to provide data in a simple package we can pass on to rendering. We saw this in the [request life-cycle guide](request_lifecycle.html) when we added a messenger parameter to our `show` route in `lib/hello_web/controllers/hello_controller.ex`. ``` defmodule HelloWeb.HelloController do ... def show(conn, %{"messenger" => messenger}) do render(conn, :show, messenger: messenger) end end ``` In some cases — often in `index` actions, for instance — we don't care about parameters because our behavior doesn't depend on them. In those cases, we don't use the incoming parameters, and simply prefix the variable name with an underscore, calling it `_params`. This will keep the compiler from complaining about the unused variable while still keeping the correct arity. ## [](controllers.html#rendering)Rendering Controllers can render content in several ways. The simplest is to render some plain text using the [`text/2`](Phoenix.Controller.html#text/2) function which Phoenix provides. For example, let's rewrite the `show` action from `HelloController` to return text instead. For that, we could do the following. ``` def show(conn, %{"messenger" => messenger}) do text(conn, "From messenger #{messenger}") end ``` Now [`/hello/Frank`](http://localhost:4000/hello/Frank) in your browser should display `From messenger Frank` as plain text without any HTML. A step beyond this is rendering pure JSON with the [`json/2`](Phoenix.Controller.html#json/2) function. We need to pass it something that the [Jason library](../jason/1.4.0/Jason.html) can decode into JSON, such as a map. (Jason is one of Phoenix's dependencies.) ``` def show(conn, %{"messenger" => messenger}) do json(conn, %{id: messenger}) end ``` If we again visit [`/hello/Frank`](http://localhost:4000/hello/Frank) in the browser, we should see a block of JSON with the key `id` mapped to the string `"Frank"`. ``` {"id": "Frank"} ``` The [`json/2`](Phoenix.Controller.html#json/2) function is useful for writing APIs and there is also the [`html/2`](Phoenix.Controller.html#html/2) function for rendering HTML, but most of the times we use Phoenix views to build our responses. For this, Phoenix includes the [`render/3`](Phoenix.Controller.html#render/3) function. It is specially important for HTML responses, as Phoenix Views provide performance and security benefits. Let's rollback our `show` action to what we originally wrote in the [request life-cycle guide](request_lifecycle.html): ``` defmodule HelloWeb.HelloController do use HelloWeb, :controller def show(conn, %{"messenger" => messenger}) do render(conn, :show, messenger: messenger) end end ``` In order for the [`render/3`](Phoenix.Controller.html#render/3) function to work correctly, the controller and view must share the same root name (in this case `Hello`), and the `HelloHTML` module must include an `embed_templates` definition specifying where its templates live. By default the controller, view module, and templates are collocated together in the same controller directory. In other words, `HelloController` requires `HelloHTML`, and `HelloHTML` requires the existence of the `lib/hello_web/controllers/hello_html/` directory, which must contain the `show.html.heex` template. [`render/3`](Phoenix.Controller.html#render/3) will also pass the value which the `show` action received for `messenger` from the parameters as an assign. If we need to pass values into the template when using `render`, that's easy. We can pass a keyword like we've seen with `messenger: messenger`, or we can use [`Plug.Conn.assign/3`](../plug/1.15.3/Plug.Conn.html#assign/3), which conveniently returns `conn`. ``` def show(conn, %{"messenger" => messenger}) do conn |> Plug.Conn.assign(:messenger, messenger) |> render(:show) end ``` Note: Using [`Phoenix.Controller`](Phoenix.Controller.html) imports [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html), so shortening the call to [`assign/3`](../plug/1.15.3/Plug.Conn.html#assign/3) works just fine. Passing more than one value to our template is as simple as connecting [`assign/3`](../plug/1.15.3/Plug.Conn.html#assign/3) functions together: ``` def show(conn, %{"messenger" => messenger}) do conn |> assign(:messenger, messenger) |> assign(:receiver, "Dweezil") |> render(:show) end ``` Or you can pass the assigns directly to `render` instead: ``` def show(conn, %{"messenger" => messenger}) do render(conn, :show, messenger: messenger, receiver: "Dweezil") end ``` Generally speaking, once all assigns are configured, we invoke the view layer. The view layer (`HelloWeb.HelloHTML`) then renders `show.html` alongside the layout and a response is sent back to the browser. [Components and HEEx templates](components.html) have their own guide, so we won't spend much time on them here. What we will look at is how to render different formats from inside a controller action. ## [](controllers.html#new-rendering-formats)New rendering formats Rendering HTML through a template is fine, but what if we need to change the rendering format on the fly? Let's say that sometimes we need HTML, sometimes we need plain text, and sometimes we need JSON. Then what? The view's job is not only to render HTML templates. Views are about data presentation. Given a bag of data, the view's purpose is to present that in a meaningful way given some format, be it HTML, JSON, CSV, or others. Many web apps today return JSON to remote clients, and Phoenix views are *great* for JSON rendering. As an example, let's take `PageController`'s `home` action from a newly generated app. Out of the box, this has the right view `PageHTML`, the embedded templates from (`lib/hello_web/controllers/page_html`), and the right template for rendering HTML (`home.html.heex`.) ``` def home(conn, _params) do render(conn, :home, layout: false) end ``` What it doesn't have is a view for rendering JSON. Phoenix Controller hands off to a view module to render templates, and it does so per format. We already have a view for the HTML format, but we need to instruct Phoenix how to render the JSON format as well. By default, you can see which formats your controllers support in `lib/hello_web.ex`: ``` def controller do quote do use Phoenix.Controller, formats: [:html, :json], layouts: [html: HelloWeb.Layouts] ... end end ``` So out of the box Phoenix will look for a `HTML` and [`JSON`](../elixir/JSON.html) view modules based on the request format and the controller name. We can also explicitly tell Phoenix in our controller which view(s) to use for each format. For example, what Phoenix does by default can be explicitly set with the following in your controller: ``` plug :put_view, html: HelloWeb.PageHTML, json: HelloWeb.PageJSON ``` Let's add a `PageJSON` view module at `lib/hello_web/controllers/page_json.ex`: ``` defmodule HelloWeb.PageJSON do def home(_assigns) do %{message: "this is some JSON"} end end ``` Since the Phoenix View layer is simply a function that the controller renders, passing connection assigns, we can define a regular `home/1` function and return a map to be serialized as JSON. There are just a few more things we need to do to make this work. Because we want to render both HTML and JSON from the same controller, we need to tell our router that it should accept the `json` format. We do that by adding `json` to the list of accepted formats in the `:browser` pipeline. Let's open up `lib/hello_web/router.ex` and change `plug :accepts` to include `json` as well as `html` like this. ``` defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do plug :accepts, ["html", "json"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.LayoutView, :root} plug :protect_from_forgery plug :put_secure_browser_headers end ... ``` Phoenix allows us to change formats on the fly with the `_format` query string parameter. If we go to [`http://localhost:4000/?_format=json`](http://localhost:4000/?_format=json), we will see `%{"message": "this is some JSON"}`. In practice, however, applications that need to render both formats typically use two distinct pipelines for each, such as the `pipeline :api` already defined in your router file. To learn more, see [our JSON and APIs guide](json_and_apis.html). ### [](controllers.html#sending-responses-directly)Sending responses directly If none of the rendering options above quite fits our needs, we can compose our own using some of the functions that [`Plug`](../plug/1.15.3/Plug.html) gives us. Let's say we want to send a response with a status of "201" and no body whatsoever. We can do that with the [`Plug.Conn.send_resp/3`](../plug/1.15.3/Plug.Conn.html#send_resp/3) function. Edit the `home` action of `PageController` in `lib/hello_web/controllers/page_controller.ex` to look like this: ``` def home(conn, _params) do send_resp(conn, 201, "") end ``` Reloading [http://localhost:4000](http://localhost:4000) should show us a completely blank page. The network tab of our browser's developer tools should show a response status of "201" (Created). Some browsers (Safari) will download the response, as the content type is not set. To be specific about the content type, we can use [`put_resp_content_type/2`](../plug/1.15.3/Plug.Conn.html#put_resp_content_type/2) in conjunction with [`send_resp/3`](../plug/1.15.3/Plug.Conn.html#send_resp/3). ``` def home(conn, _params) do conn |> put_resp_content_type("text/plain") |> send_resp(201, "") end ``` Using [`Plug`](../plug/1.15.3/Plug.html) functions in this way, we can craft just the response we need. ### [](controllers.html#setting-the-content-type)Setting the content type Analogous to the `_format` query string param, we can render any sort of format we want by modifying the HTTP Content-Type Header and providing the appropriate template. If we wanted to render an XML version of our `home` action, we might implement the action like this in `lib/hello_web/page_controller.ex`. ``` def home(conn, _params) do conn |> put_resp_content_type("text/xml") |> render(:home, content: some_xml_content) end ``` We would then need to provide an `home.xml.eex` template which created valid XML, and we would be done. For a list of valid content mime-types, please see the [`MIME`](../mime/2.0.5/MIME.html) library. ### [](controllers.html#setting-the-http-status)Setting the HTTP Status We can also set the HTTP status code of a response similarly to the way we set the content type. The [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) module, imported into all controllers, has a `put_status/2` function to do this. [`Plug.Conn.put_status/2`](../plug/1.15.3/Plug.Conn.html#put_status/2) takes `conn` as the first parameter and as the second parameter either an integer or a "friendly name" used as an atom for the status code we want to set. The list of status code atom representations can be found in [`Plug.Conn.Status.code/1`](../plug/1.15.3/Plug.Conn.Status.html#code/1) documentation. Let's change the status in our `PageController` `home` action. ``` def home(conn, _params) do conn |> put_status(202) |> render(:home, layout: false) end ``` The status code we provide must be a valid number. ## [](controllers.html#redirection)Redirection Often, we need to redirect to a new URL in the middle of a request. A successful `create` action, for instance, will usually redirect to the `show` action for the resource we just created. Alternately, it could redirect to the `index` action to show all the things of that same type. There are plenty of other cases where redirection is useful as well. Whatever the circumstance, Phoenix controllers provide the handy [`redirect/2`](Phoenix.Controller.html#redirect/2) function to make redirection easy. Phoenix differentiates between redirecting to a path within the application and redirecting to a URL — either within our application or external to it. In order to try out [`redirect/2`](Phoenix.Controller.html#redirect/2), let's create a new route in `lib/hello_web/router.ex`. ``` defmodule HelloWeb.Router do ... scope "/", HelloWeb do ... get "/", PageController, :home get "/redirect_test", PageController, :redirect_test ... end end ``` Then we'll change `PageController`'s `home` action of our controller to do nothing but to redirect to our new route. ``` defmodule HelloWeb.PageController do use HelloWeb, :controller def home(conn, _params) do redirect(conn, to: ~p"/redirect_test") end end ``` We made use of [`Phoenix.VerifiedRoutes.sigil_p/2`](Phoenix.VerifiedRoutes.html#sigil_p/2) to build our redirect path, which is the preferred approach to reference any path within our application. We learned about verified routes in the [routing guide](routing.html). Finally, let's define in the same file the action we redirect to, which simply renders the home, but now under a new address: ``` def redirect_test(conn, _params) do render(conn, :home, layout: false) end ``` When we reload our [welcome page](http://localhost:4000/), we see that we've been redirected to `/redirect_test` which shows the original welcome page. It works! If we care to, we can open up our developer tools, click on the network tab, and visit our root route again. We see two main requests for this page - a get to `/` with a status of `302`, and a get to `/redirect_test` with a status of `200`. Notice that the redirect function takes `conn` as well as a string representing a relative path within our application. For security reasons, the `:to` option can only redirect to paths within your application. If you want to redirect to a fully-qualified path or an external URL, you should use `:external` instead: ``` def home(conn, _params) do redirect(conn, external: "https://elixir-lang.org/") end ``` ## [](controllers.html#flash-messages)Flash messages Sometimes we need to communicate with users during the course of an action. Maybe there was an error updating a schema, or maybe we just want to welcome them back to the application. For this, we have flash messages. The [`Phoenix.Controller`](Phoenix.Controller.html) module provides the [`put_flash/3`](Phoenix.Controller.html#put_flash/3) to set flash messages as a key-value pair and placing them into a `@flash` assign in the connection. Let's set two flash messages in our `HelloWeb.PageController` to try this out. To do this we modify the `home` action as follows: ``` defmodule HelloWeb.PageController do ... def home(conn, _params) do conn |> put_flash(:error, "Let's pretend we have an error.") |> render(:home, layout: false) end end ``` In order to see our flash messages, we need to be able to retrieve them and display them in a template layout. We can do that using [`Phoenix.Flash.get/2`](Phoenix.Flash.html#get/2) which takes the flash data and the key we care about. It then returns the value for that key. For our convenience, a `flash_group` component is already available and added to the beginning of our [welcome page](http://localhost:4000/) ``` <.flash_group flash={@flash} /> ``` When we reload the [welcome page](http://localhost:4000/), our message should appear in the top right corner of the page. The flash functionality is handy when mixed with redirects. Perhaps you want to redirect to a page with some extra information. If we reuse the redirect action from the previous section, we can do: ``` def home(conn, _params) do conn |> put_flash(:error, "Let's pretend we have an error.") |> redirect(to: ~p"/redirect_test") end ``` Now if you reload the [welcome page](http://localhost:4000/), you will be redirected and the flash message will be shown once more. Besides [`put_flash/3`](Phoenix.Controller.html#put_flash/3), the [`Phoenix.Controller`](Phoenix.Controller.html) module has another useful function worth knowing about. [`clear_flash/1`](Phoenix.Controller.html#clear_flash/1) takes only `conn` and removes any flash messages which might be stored in the session. Phoenix does not enforce which keys are stored in the flash. As long as we are internally consistent, all will be well. `:info` and `:error`, however, are common and are handled by default in our templates. ## [](controllers.html#error-pages)Error pages Phoenix has two views called `ErrorHTML` and `ErrorJSON` which live in `lib/hello_web/controllers/`. The purpose of these views is to handle errors in a general way for incoming HTML or JSON requests. Similar to the views we built in this guide, error views can return both HTML and JSON responses. See the [Custom Error Pages How-To](custom_error_pages.html) for more information. [← Previous Page Routing](routing.html) [Next Page → Components and HEEx](components.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/controllers.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/custom_error_pages.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/howto/custom_error_pages.md#L1 "View Source") Custom Error Pages New Phoenix projects have two error views called `ErrorHTML` and `ErrorJSON`, which live in `lib/hello_web/controllers/`. The purpose of these views is to handle errors in a general way for each format, from one centralized location. ## [](custom_error_pages.html#the-error-views)The Error Views For new applications, the `ErrorHTML` and `ErrorJSON` views looks like this: ``` defmodule HelloWeb.ErrorHTML do use HelloWeb, :html # If you want to customize your error pages, # uncomment the embed_templates/1 call below # and add pages to the error directory: # # * lib/<%= @lib_web_name %>/controllers/error_html/404.html.heex # * lib/<%= @lib_web_name %>/controllers/error_html/500.html.heex # # embed_templates "error_html/*" # The default is to render a plain text page based on # the template name. For example, "404.html" becomes # "Not Found". def render(template, _assigns) do Phoenix.Controller.status_message_from_template(template) end end defmodule HelloWeb.ErrorJSON do # If you want to customize a particular status code, # you may add your own clauses, such as: # # def render("500.json", _assigns) do # %{errors: %{detail: "Internal Server Error"}} # end # By default, Phoenix returns the status message from # the template name. For example, "404.json" becomes # "Not Found". def render(template, _assigns) do %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} end end ``` Before we dive into this, let's see what the rendered `404 Not Found` message looks like in a browser. In the development environment, Phoenix will debug errors by default, showing us a very informative debugging page. What we want here, however, is to see what page the application would serve in production. In order to do that, we need to set `debug_errors: false` in `config/dev.exs`. ``` import Config config :hello, HelloWeb.Endpoint, http: [port: 4000], debug_errors: false, code_reloader: true, . . . ``` After modifying our config file, we need to restart our server in order for this change to take effect. After restarting the server, let's go to [http://localhost:4000/such/a/wrong/path](http://localhost:4000/such/a/wrong/path) for a running local application and see what we get. Ok, that's not very exciting. We get the bare string "Not Found", displayed without any markup or styling. The first question is, where does that error string come from? The answer is right in `ErrorHTML`. ``` def render(template, _assigns) do Phoenix.Controller.status_message_from_template(template) end ``` Great, so we have this `render/2` function that takes a template and an `assigns` map, which we ignore. When you call `render(conn, :some_template)` from the controller, Phoenix first looks for a `some_template/1` function on the view module. If no function exists, it falls back to calling `render/2` with the template and format name, such as `"some_template.html"`. In other words, to provide custom error pages, we could simply define a proper `render/2` function clause in `HelloWeb.ErrorHTML`. ``` def render("404.html", _assigns) do "Page Not Found" end ``` But we can do even better. Phoenix generates an `ErrorHTML` for us, but it doesn't give us a `lib/hello_web/controllers/error_html` directory. Let's create one now. Inside our new directory, let's add a template named `404.html.heex` and give it some markup – a mixture of our application layout and a new `<div>` with our message to the user. ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Welcome to Phoenix!</title> <link rel="stylesheet" href="/assets/app.css"/> <script defer type="text/javascript" src="/assets/app.js"></script> </head> <body> <header> <section class="container"> <nav> <ul> <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li> </ul> </nav> <a href="https://phoenixframework.org/" class="phx-logo"> <img src="/images/logo.svg" alt="Phoenix Framework Logo"/> </a> </section> </header> <main class="container"> <section class="phx-hero"> <p>Sorry, the page you are looking for does not exist.</p> </section> </main> </body> </html> ``` After you define the template file, remember to remove the equivalent `render/2` clause for that template, as otherwise the function overrides the template. Let's do so for the 404.html clause we have previously introduced in `lib/hello_web/controllers/error_html.ex`. We also need to tell Phoenix to embed our templates into the module: ``` + embed_templates "error_html/*" - def render("404.html", _assigns) do - "Page Not Found" - end ``` Now, when we go back to [http://localhost:4000/such/a/wrong/path](http://localhost:4000/such/a/wrong/path), we should see a much nicer error page. It is worth noting that we did not render our `404.html.heex` template through our application layout, even though we want our error page to have the look and feel of the rest of our site. This is to avoid circular errors. For example, what happens if our application failed due to an error in the layout? Attempting to render the layout again will just trigger another error. So ideally we want to minimize the amount of dependencies and logic in our error templates, sharing only what is necessary. ## [](custom_error_pages.html#custom-exceptions)Custom exceptions Elixir provides a macro called [`defexception/1`](../elixir/Kernel.html#defexception/1) for defining custom exceptions. Exceptions are represented as structs, and structs need to be defined inside of modules. In order to create a custom exception, we need to define a new module. Conventionally, this will have "Error" in the name. Inside that module, we need to define a new exception with [`defexception/1`](../elixir/Kernel.html#defexception/1), the file `lib/hello_web.ex` seems like a good place for it. ``` defmodule HelloWeb.SomethingNotFoundError do defexception [:message] end ``` You can raise your new exception like this: ``` raise HelloWeb.SomethingNotFoundError, "oops" ``` By default, Plug and Phoenix will treat all exceptions as 500 errors. However, Plug provides a protocol called [`Plug.Exception`](../plug/1.15.3/Plug.Exception.html) where we are able to customize the status and add actions that exception structs can return on the debug error page. If we wanted to supply a status of 404 for an `HelloWeb.SomethingNotFoundError` error, we could do it by defining an implementation for the [`Plug.Exception`](../plug/1.15.3/Plug.Exception.html) protocol like this, in `lib/hello_web.ex`: ``` defimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do def status(_exception), do: 404 def actions(_exception), do: [] end ``` Alternatively, you could define a `plug_status` field directly in the exception struct: ``` defmodule HelloWeb.SomethingNotFoundError do defexception [:message, plug_status: 404] end ``` However, implementing the [`Plug.Exception`](../plug/1.15.3/Plug.Exception.html) protocol by hand can be convenient in certain occasions, such as when providing actionable errors. ## [](custom_error_pages.html#actionable-errors)Actionable errors Exception actions are functions that can be triggered from the error page, and they're basically a list of maps defining a `label` and a `handler` to be executed. As an example, Phoenix will display an error if you have pending migrations and will provide a button on the error page to perform the pending migrations. When `debug_errors` is `true`, they are rendered in the error page as a collection of buttons and follow the format of: ``` [ %{ label: String.t(), handler: {module(), function :: atom(), args :: []} } ] ``` If we wanted to return some actions for an `HelloWeb.SomethingNotFoundError` we would implement [`Plug.Exception`](../plug/1.15.3/Plug.Exception.html) like this: ``` defimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do def status(_exception), do: 404 def actions(_exception) do [ %{ label: "Run seeds", handler: {Code, :eval_file, ["priv/repo/seeds.exs"]} } ] end end ``` [← Previous Page Routing cheatsheet](router.html) [Next Page → File Uploads](file_uploads.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/howto/custom_error_pages.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/deployment.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/deployment/deployment.md#L1 "View Source") Introduction to Deployment Once we have a working application, we're ready to deploy it. If you're not quite finished with your own application, don't worry. Just follow the [Up and Running Guide](up_and_running.html) to create a basic application to work with. When preparing an application for deployment, there are three main steps: - Handling of your application secrets - Compiling your application assets - Starting your server in production In this guide, we will learn how to get the production environment running locally. You can use the same techniques in this guide to run your application in production, but depending on your deployment infrastructure, extra steps will be necessary. As an example of deploying to other infrastructures, we also discuss four different approaches in our guides: using [Elixir's releases](releases.html) with [`mix release`](../mix/Mix.Tasks.Release.html), [using Gigalixir](gigalixir.html), [using Fly](fly.html), and [using Heroku](heroku.html). We've also included links to deploying Phoenix on other platforms under [Community Deployment Guides](deployment.html#community-deployment-guides). Finally, the release guide has a sample Dockerfile you can use if you prefer to deploy with container technologies. Let's explore those steps above one by one. ## [](deployment.html#handling-of-your-application-secrets)Handling of your application secrets All Phoenix applications have data that must be kept secure, for example, the username and password for your production database, and the secret Phoenix uses to sign and encrypt important information. The general recommendation is to keep those in environment variables and load them into your application. This is done in `config/runtime.exs` (formerly `config/prod.secret.exs` or `config/releases.exs`), which is responsible for loading secrets and configuration from environment variables. Therefore, you need to make sure the proper relevant variables are set in production: ``` $ mix phx.gen.secret REALLY_LONG_SECRET $ export SECRET_KEY_BASE=REALLY_LONG_SECRET $ export DATABASE_URL=ecto://USER:PASS@HOST/database ``` Do not copy those values directly, set `SECRET_KEY_BASE` according to the result of [`mix phx.gen.secret`](Mix.Tasks.Phx.Gen.Secret.html) and `DATABASE_URL` according to your database address. If for some reason you do not want to rely on environment variables, you can hard code the secrets in your `config/runtime.exs` but make sure not to check the file into your version control system. With your secret information properly secured, it is time to configure assets! Before taking this step, we need to do one bit of preparation. Since we will be readying everything for production, we need to do some setup in that environment by getting our dependencies and compiling. ``` $ mix deps.get --only prod $ MIX_ENV=prod mix compile ``` ## [](deployment.html#compiling-your-application-assets)Compiling your application assets This step is required only if you have compilable assets like JavaScript and stylesheets. By default, Phoenix uses `esbuild` but everything is encapsulated in a single `mix assets.deploy` task defined in your `mix.exs`: ``` $ MIX_ENV=prod mix assets.deploy Check your digested files at "priv/static". ``` And that is it! The Mix task by default builds the assets and then generates digests with a cache manifest file so Phoenix can quickly serve assets in production. > Note: if you run the task above in your local machine, it will generate many digested assets in `priv/static`. You can prune them by running `mix phx.digest.clean --all`. Keep in mind that, if you by any chance forget to run the steps above, Phoenix will show an error message: ``` $ PORT=4001 MIX_ENV=prod mix phx.server 10:50:18.732 [info] Running MyAppWeb.Endpoint with Cowboy on http://example.com 10:50:18.735 [error] Could not find static manifest at "my_app/_build/prod/lib/foo/priv/static/cache_manifest.json". Run "mix phx.digest" after building your static files or remove the configuration from "config/prod.exs". ``` The error message is quite clear: it says Phoenix could not find a static manifest. Just run the commands above to fix it or, if you are not serving or don't care about assets at all, you can just remove the `cache_static_manifest` configuration from your config. ## [](deployment.html#starting-your-server-in-production)Starting your server in production To run Phoenix in production, we need to set the `PORT` and `MIX_ENV` environment variables when invoking [`mix phx.server`](Mix.Tasks.Phx.Server.html): ``` $ PORT=4001 MIX_ENV=prod mix phx.server 10:59:19.136 [info] Running MyAppWeb.Endpoint with Cowboy on http://example.com ``` To run in detached mode so that the Phoenix server does not stop and continues to run even if you close the terminal: ``` $ PORT=4001 MIX_ENV=prod elixir --erl "-detached" -S mix phx.server ``` In case you get an error message, please read it carefully, and open up a bug report if it is still not clear how to address it. You can also run your application inside an interactive shell: ``` $ PORT=4001 MIX_ENV=prod iex -S mix phx.server 10:59:19.136 [info] Running MyAppWeb.Endpoint with Cowboy on http://example.com ``` ## [](deployment.html#putting-it-all-together)Putting it all together The previous sections give an overview about the main steps required to deploy your Phoenix application. In practice, you will end-up adding steps of your own as well. For example, if you are using a database, you will also want to run [`mix ecto.migrate`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Migrate.html) before starting the server to ensure your database is up to date. Overall, here is a script you can use as a starting point: ``` # Initial setup $ mix deps.get --only prod $ MIX_ENV=prod mix compile # Compile assets $ MIX_ENV=prod mix assets.deploy # Custom tasks (like DB migrations) $ MIX_ENV=prod mix ecto.migrate # Finally run the server $ PORT=4001 MIX_ENV=prod mix phx.server ``` And that's it. Next, you can use one of our official guides to deploy: - [with Elixir's releases](releases.html) - [to Gigalixir](gigalixir.html), an Elixir-centric Platform as a Service (PaaS) - [to Fly.io](fly.html), a PaaS that deploys your servers close to your users with built-in distribution support - and [to Heroku](heroku.html), one of the most popular PaaS. ## [](deployment.html#community-deployment-guides)Community Deployment Guides - [Render](https://render.com) has first class support for Phoenix applications. There are guides for hosting Phoenix with [Mix releases](https://render.com/docs/deploy-phoenix), [Distillery](https://render.com/docs/deploy-phoenix-distillery), and as a [Distributed Elixir Cluster](https://render.com/docs/deploy-elixir-cluster). [← Previous Page Testing Channels](testing_channels.html) [Next Page → Deploying with Releases](releases.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/deployment/deployment.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/directory_structure.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/directory_structure.md#L1 "View Source") Directory structure > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). When we use [`mix phx.new`](Mix.Tasks.Phx.New.html) to generate a new Phoenix application, it builds a top-level directory structure like this: ``` ├── _build ├── assets ├── config ├── deps ├── lib │   ├── hello │   ├── hello.ex │   ├── hello_web │   └── hello_web.ex ├── priv └── test ``` We will go over those directories one by one: - `_build` - a directory created by the `mix` command line tool that ships as part of Elixir that holds all compilation artifacts. As we have seen in "[Up and Running](up_and_running.html)", `mix` is the main interface to your application. We use Mix to compile our code, create databases, run our server, and more. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to rebuild your application from scratch. - `assets` - a directory that keeps source code for your front-end assets, typically JavaScript and CSS. These sources are automatically bundled by the `esbuild` tool. Static files like images and fonts go in `priv/static`. - `config` - a directory that holds your project configuration. The `config/config.exs` file is the entry point for your configuration. At the end of the `config/config.exs`, it imports environment specific configuration, which can be found in `config/dev.exs`, `config/test.exs`, and `config/prod.exs`. Finally, `config/runtime.exs` is executed and it is the best place to read secrets and other dynamic configuration. - `deps` - a directory with all of our Mix dependencies. You can find all dependencies listed in the `mix.exs` file, inside the `defp deps do` function definition. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to download all deps from scratch. - `lib` - a directory that holds your application source code. This directory is broken into two subdirectories, `lib/hello` and `lib/hello_web`. The `lib/hello` directory will be responsible to host all of your business logic and business domain. It typically interacts directly with the database - it is the "Model" in Model-View-Controller (MVC) architecture. `lib/hello_web` is responsible for exposing your business domain to the world, in this case, through a web application. It holds both the View and Controller from MVC. We will discuss the contents of these directories with more detail in the next sections. - `priv` - a directory that keeps all resources that are necessary in production but are not directly part of your source code. You typically keep database scripts, translation files, images, and more in here. Generated assets, created from files in the `assets` directory, are placed in `priv/static/assets` by default. - `test` - a directory with all of our application tests. It often mirrors the same structure found in `lib`. ## [](directory_structure.html#the-lib-hello-directory)The lib/hello directory The `lib/hello` directory hosts all of your business domain. Since our project does not have any business logic yet, the directory is mostly empty. You will only find three files: ``` lib/hello ├── application.ex ├── mailer.ex └── repo.ex ``` The `lib/hello/application.ex` file defines an Elixir application named `Hello.Application`. That's because at the end of the day Phoenix applications are simply Elixir applications. The `Hello.Application` module defines which services are part of our application: ``` children = [ HelloWeb.Telemetry, Hello.Repo, {Phoenix.PubSub, name: Hello.PubSub}, HelloWeb.Endpoint ] ``` If it is your first time with Phoenix, you don't need to worry about the details right now. For now, suffice it to say our application starts a database repository, a PubSub system for sharing messages across processes and nodes, and the application endpoint, which effectively serves HTTP requests. These services are started in the order they are defined and, whenever shutting down your application, they are stopped in the reverse order. You can learn more about applications in [Elixir's official docs for Application](../elixir/Application.html). The `lib/hello/mailer.ex` file holds the `Hello.Mailer` module, which defines the main interface to deliver e-mails: ``` defmodule Hello.Mailer do use Swoosh.Mailer, otp_app: :hello end ``` In the same `lib/hello` directory, we will find a `lib/hello/repo.ex`. It defines a `Hello.Repo` module which is our main interface to the database. If you are using Postgres (the default database), you will see something like this: ``` defmodule Hello.Repo do use Ecto.Repo, otp_app: :hello, adapter: Ecto.Adapters.Postgres end ``` And that's it for now. As you work on your project, we will add files and modules to this directory. ## [](directory_structure.html#the-lib-hello_web-directory)The lib/hello\_web directory The `lib/hello_web` directory holds the web-related parts of our application. It looks like this when expanded: ``` lib/hello_web ├── controllers │   ├── page_controller.ex │   ├── page_html.ex │   ├── error_html.ex │   ├── error_json.ex │   └── page_html │   └── home.html.heex ├── components │   ├── core_components.ex │   ├── layouts.ex │   └── layouts │   ├── app.html.heex │   └── root.html.heex ├── endpoint.ex ├── gettext.ex ├── router.ex └── telemetry.ex ``` All of the files which are currently in the `controllers` and `components` directories are there to create the "Welcome to Phoenix!" page we saw in the "[Up and running](up_and_running.html)" guide. By looking at `controller` and `components` directories, we can see Phoenix provides features for handling layouts and HTML and error pages out of the box. Besides the directories mentioned, `lib/hello_web` has four files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses [http://localhost:4000](http://localhost:4000), the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to "controllers", which calls a view module to render HTML pages back to clients. We explore these layers in length in other guides, starting with the "[Request life-cycle](request_lifecycle.html)" guide coming next. Through *Telemetry*, Phoenix is able to collect metrics and send monitoring events of your application. The `lib/hello_web/telemetry.ex` file defines the supervisor responsible for managing the telemetry processes. You can find more information on this topic in the [Telemetry guide](telemetry.html). Finally, there is a `lib/hello_web/gettext.ex` file which provides internationalization through [Gettext](../gettext/Gettext.html). If you are not worried about internationalization, you can safely skip this file and its contents. ## [](directory_structure.html#the-assets-directory)The assets directory The `assets` directory contains source files related to front-end assets, such as JavaScript and CSS. Since Phoenix v1.6, we use [`esbuild`](https://github.com/evanw/esbuild/) to compile assets, which is managed by the [`esbuild`](https://github.com/phoenixframework/esbuild) Elixir package. The integration with `esbuild` is baked into your app. The relevant config can be found in your `config/config.exs` file. Your other static assets are placed in the `priv/static` folder, where `priv/static/assets` is kept for generated assets. Everything in `priv/static` is served by the [`Plug.Static`](../plug/1.15.3/Plug.Static.html) plug configured in `lib/hello_web/endpoint.ex`. When running in dev mode (`MIX_ENV=dev`), Phoenix watches for any changes you make in the `assets` directory, and then takes care of updating your front end application in your browser as you work. Note that when you first create your Phoenix app using [`mix phx.new`](Mix.Tasks.Phx.New.html) it is possible to specify options that will affect the presence and layout of the `assets` directory. In fact, Phoenix apps can bring their own front end tools or not have a front-end at all (handy if you're writing an API for example). For more information you can run [`mix help phx.new`](Mix.Tasks.Phx.New.html) or see the documentation in [Mix tasks](mix_tasks.html). If the default esbuild integration does not cover your needs, for example because you want to use another build tool, you can switch to a [custom assets build](asset_management.html#custom_builds). As for CSS, Phoenix ships with the [Tailwind CSS Framework](https://tailwindcss.com/), providing a base setup for projects. You may move to any CSS framework of your choice. Additional references can be found in the [asset management](asset_management.html#css) guide. [← Previous Page Packages Glossary](packages_glossary.html) [Next Page → Request life-cycle](request_lifecycle.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/directory_structure.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/ecto.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/ecto.md#L1 "View Source") Ecto > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). Most web applications today need some form of data validation and persistence. In the Elixir ecosystem, we have [`Ecto`](../ecto/3.10.1/Ecto.html) to enable this. Before we jump into building database-backed web features, we're going to focus on the finer details of Ecto to give a solid base to build our web features on top of. Let's get started! Phoenix uses Ecto to provide builtin support to the following databases: - PostgreSQL (via [`postgrex`](https://github.com/elixir-ecto/postgrex)) - MySQL (via [`myxql`](https://github.com/elixir-ecto/myxql)) - MSSQL (via [`tds`](https://github.com/livehelpnow/tds)) - ETS (via [`etso`](https://github.com/evadne/etso)) - SQLite3 (via [`ecto_sqlite3`](https://github.com/elixir-sqlite/ecto_sqlite3)) Newly generated Phoenix projects include Ecto with the PostgreSQL adapter by default. You can pass the `--database` option to change or `--no-ecto` flag to exclude this. Ecto also provides support for other databases and it has many learning resources available. Please check out [Ecto's README](https://github.com/elixir-ecto/ecto) for general information. This guide assumes that we have generated our new application with Ecto integration and that we will be using PostgreSQL. The introductory guides cover how to get your first application up and running. For using other databases, see the [Using other databases](ecto.html#using-other-databases) section. ## [](ecto.html#using-the-schema-and-migration-generator)Using the schema and migration generator Once we have Ecto and PostgreSQL installed and configured, the easiest way to use Ecto is to generate an Ecto *schema* through the `phx.gen.schema` task. Ecto schemas are a way for us to specify how Elixir data types map to and from external sources, such as database tables. Let's generate a `User` schema with `name`, `email`, `bio`, and `number_of_pets` fields. ``` $ mix phx.gen.schema User users name:string email:string \ bio:string number_of_pets:integer * creating ./lib/hello/user.ex * creating priv/repo/migrations/20170523151118_create_users.exs Remember to update your repository by running migrations: $ mix ecto.migrate ``` A couple of files were generated with this task. First, we have a `user.ex` file, containing our Ecto schema with our schema definition of the fields we passed to the task. Next, a migration file was generated inside `priv/repo/migrations/` which will create our database table that our schema maps to. With our files in place, let's follow the instructions and run our migration: ``` $ mix ecto.migrate Compiling 1 file (.ex) Generated hello app [info] == Running Hello.Repo.Migrations.CreateUsers.change/0 forward [info] create table users [info] == Migrated in 0.0s ``` Mix assumes that we are in the development environment unless we tell it otherwise with `MIX_ENV=prod mix ecto.migrate`. If we log in to our database server, and connect to our `hello_dev` database, we should see our `users` table. Ecto assumes that we want an integer column called `id` as our primary key, so we should see a sequence generated for that as well. ``` $ psql -U postgres Type "help" for help. postgres=# \connect hello_dev You are now connected to database "hello_dev" as user "postgres". hello_dev=# \d List of relations Schema | Name | Type | Owner --------+-------------------+----------+---------- public | schema_migrations | table | postgres public | users | table | postgres public | users_id_seq | sequence | postgres (3 rows) hello_dev=# \q ``` If we take a look at the migration generated by `phx.gen.schema` in `priv/repo/migrations/`, we'll see that it will add the columns we specified. It will also add timestamp columns for `inserted_at` and `updated_at` which come from the [`timestamps/1`](../ecto_sql/3.10.1/Ecto.Migration.html#timestamps/1) function. ``` defmodule Hello.Repo.Migrations.CreateUsers do use Ecto.Migration def change do create table(:users) do add :name, :string add :email, :string add :bio, :string add :number_of_pets, :integer timestamps() end end end ``` And here's what that translates to in the actual `users` table. ``` $ psql hello_dev=# \d users Table "public.users" Column | Type | Modifiers ---------------+-----------------------------+---------------------------------------------------- id | bigint | not null default nextval('users_id_seq'::regclass) name | character varying(255) | email | character varying(255) | bio | character varying(255) | number_of_pets | integer | inserted_at | timestamp without time zone | not null updated_at | timestamp without time zone | not null Indexes: "users_pkey" PRIMARY KEY, btree (id) ``` Notice that we do get an `id` column as our primary key by default, even though it isn't listed as a field in our migration. ## [](ecto.html#repo-configuration)Repo configuration Our `Hello.Repo` module is the foundation we need to work with databases in a Phoenix application. Phoenix generated it for us in `lib/hello/repo.ex`, and this is what it looks like. ``` defmodule Hello.Repo do use Ecto.Repo, otp_app: :hello, adapter: Ecto.Adapters.Postgres end ``` It begins by defining the repository module. Then it configures our `otp_app` name, and the `adapter` – `Postgres`, in our case. Our repo has three main tasks - to bring in all the common query functions from \[[`Ecto.Repo`](../ecto/3.10.1/Ecto.Repo.html)], to set the `otp_app` name equal to our application name, and to configure our database adapter. We'll talk more about how to use `Hello.Repo` in a bit. When `phx.new` generated our application, it included some basic repository configuration as well. Let's look at `config/dev.exs`. ``` ... # Configure your database config :hello, Hello.Repo, username: "postgres", password: "postgres", hostname: "localhost", database: "hello_dev", show_sensitive_data_on_connection_error: true, pool_size: 10 ... ``` We also have similar configuration in `config/test.exs` and `config/runtime.exs` (formerly `config/prod.secret.exs`) which can also be changed to match your actual credentials. ## [](ecto.html#the-schema)The schema Ecto schemas are responsible for mapping Elixir values to external data sources, as well as mapping external data back into Elixir data structures. We can also define relationships to other schemas in our applications. For example, our `User` schema might have many posts, and each post would belong to a user. Ecto also handles data validation and type casting with changesets, which we'll discuss in a moment. Here's the `User` schema that Phoenix generated for us. ``` defmodule Hello.User do use Ecto.Schema import Ecto.Changeset schema "users" do field :bio, :string field :email, :string field :name, :string field :number_of_pets, :integer timestamps() end @doc false def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :bio, :number_of_pets]) |> validate_required([:name, :email, :bio, :number_of_pets]) end end ``` Ecto schemas at their core are simply Elixir structs. Our `schema` block is what tells Ecto how to cast our `%User{}` struct fields to and from the external `users` table. Often, the ability to simply cast data to and from the database isn't enough and extra data validation is required. This is where Ecto changesets come in. Let's dive in! ## [](ecto.html#changesets-and-validations)Changesets and validations Changesets define a pipeline of transformations our data needs to undergo before it will be ready for our application to use. These transformations might include type-casting, user input validation, and filtering out any extraneous parameters. Often we'll use changesets to validate user input before writing it to the database. Ecto repositories are also changeset-aware, which allows them not only to refuse invalid data, but also perform the minimal database updates possible by inspecting the changeset to know which fields have changed. Let's take a closer look at our default changeset function. ``` def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :bio, :number_of_pets]) |> validate_required([:name, :email, :bio, :number_of_pets]) end ``` Right now, we have two transformations in our pipeline. In the first call, we invoke [`Ecto.Changeset.cast/3`](../ecto/3.10.1/Ecto.Changeset.html#cast/3), passing in our external parameters and marking which fields are required for validation. [`cast/3`](../ecto/3.10.1/Ecto.Changeset.html#cast/3) first takes a struct, then the parameters (the proposed updates), and then the final field is the list of columns to be updated. [`cast/3`](../ecto/3.10.1/Ecto.Changeset.html#cast/3) also will only take fields that exist in the schema. Next, [`Ecto.Changeset.validate_required/3`](../ecto/3.10.1/Ecto.Changeset.html#validate_required/3) checks that this list of fields is present in the changeset that [`cast/3`](../ecto/3.10.1/Ecto.Changeset.html#cast/3) returns. By default with the generator, all fields are required. We can verify this functionality in [`IEx`](../iex/IEx.html). Let's fire up our application inside IEx by running `iex -S mix`. In order to minimize typing and make this easier to read, let's alias our `Hello.User` struct. ``` $ iex -S mix iex> alias Hello.User Hello.User ``` Next, let's build a changeset from our schema with an empty `User` struct, and an empty map of parameters. ``` iex> changeset = User.changeset(%User{}, %{}) #Ecto.Changeset< action: nil, changes: %{}, errors: [ name: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}, bio: {"can't be blank", [validation: :required]}, number_of_pets: {"can't be blank", [validation: :required]} ], data: #Hello.User<>, valid?: false > ``` Once we have a changeset, we can check if it is valid. ``` iex> changeset.valid? false ``` Since this one is not valid, we can ask it what the errors are. ``` iex> changeset.errors [ name: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}, bio: {"can't be blank", [validation: :required]}, number_of_pets: {"can't be blank", [validation: :required]} ] ``` Now, let's make `number_of_pets` optional. In order to do this, we simply remove it from the list in the `changeset/2` function, in `Hello.User`. ``` |> validate_required([:name, :email, :bio]) ``` Now casting the changeset should tell us that only `name`, `email`, and `bio` can't be blank. We can test that by running `recompile()` inside IEx and then rebuilding our changeset. ``` iex> recompile() Compiling 1 file (.ex) :ok iex> changeset = User.changeset(%User{}, %{}) #Ecto.Changeset< action: nil, changes: %{}, errors: [ name: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}, bio: {"can't be blank", [validation: :required]} ], data: #Hello.User<>, valid?: false > iex> changeset.errors [ name: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}, bio: {"can't be blank", [validation: :required]} ] ``` What happens if we pass a key-value pair that is neither defined in the schema nor required? Inside our existing IEx shell, let's create a `params` map with valid values plus an extra `random_key: "random value"`. ``` iex> params = %{name: "Joe Example", email: "joe@example.com", bio: "An example to all", number_of_pets: 5, random_key: "random value"} %{ bio: "An example to all", email: "joe@example.com", name: "Joe Example", number_of_pets: 5, random_key: "random value" } ``` Next, let's use our new `params` map to create another changeset. ``` iex> changeset = User.changeset(%User{}, params) #Ecto.Changeset< action: nil, changes: %{ bio: "An example to all", email: "joe@example.com", name: "Joe Example", number_of_pets: 5 }, errors: [], data: #Hello.User<>, valid?: true > ``` Our new changeset is valid. ``` iex> changeset.valid? true ``` We can also check the changeset's changes - the map we get after all of the transformations are complete. ``` iex(9)> changeset.changes %{bio: "An example to all", email: "joe@example.com", name: "Joe Example", number_of_pets: 5} ``` Notice that our `random_key` key and `"random_value"` value have been removed from the final changeset. Changesets allow us to cast external data, such as user input on a web form or data from a CSV file into valid data into our system. Invalid parameters will be stripped and bad data that is unable to be cast according to our schema will be highlighted in the changeset errors. We can validate more than just whether a field is required or not. Let's take a look at some finer-grained validations. What if we had a requirement that all biographies in our system must be at least two characters long? We can do this easily by adding another transformation to the pipeline in our changeset which validates the length of the `bio` field. ``` def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :bio, :number_of_pets]) |> validate_required([:name, :email, :bio, :number_of_pets]) |> validate_length(:bio, min: 2) end ``` Now, if we try to cast data containing a value of `"A"` for our user's `bio`, we should see the failed validation in the changeset's errors. ``` iex> recompile() iex> changeset = User.changeset(%User{}, %{bio: "A"}) iex> changeset.errors[:bio] {"should be at least %{count} character(s)", [count: 2, validation: :length, kind: :min, type: :string]} ``` If we also have a requirement for the maximum length that a bio can have, we can simply add another validation. ``` def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :bio, :number_of_pets]) |> validate_required([:name, :email, :bio, :number_of_pets]) |> validate_length(:bio, min: 2) |> validate_length(:bio, max: 140) end ``` Let's say we want to perform at least some rudimentary format validation on the `email` field. All we want to check for is the presence of the `@`. The [`Ecto.Changeset.validate_format/3`](../ecto/3.10.1/Ecto.Changeset.html#validate_format/3) function is just what we need. ``` def changeset(user, attrs) do user |> cast(attrs, [:name, :email, :bio, :number_of_pets]) |> validate_required([:name, :email, :bio, :number_of_pets]) |> validate_length(:bio, min: 2) |> validate_length(:bio, max: 140) |> validate_format(:email, ~r/@/) end ``` If we try to cast a user with an email of `"example.com"`, we should see an error message like the following: ``` iex> recompile() iex> changeset = User.changeset(%User{}, %{email: "example.com"}) iex> changeset.errors[:email] {"has invalid format", [validation: :format]} ``` There are many more validations and transformations we can perform in a changeset. Please see the [Ecto Changeset documentation](https://hexdocs.pm/ecto/Ecto.Changeset.html) for more information. ## [](ecto.html#data-persistence)Data persistence We've explored migrations and schemas, but we haven't yet persisted any of our schemas or changesets. We briefly looked at our repository module in `lib/hello/repo.ex` earlier, and now it's time to put it to use. Ecto repositories are the interface into a storage system, be it a database like PostgreSQL or an external service like a RESTful API. The `Repo` module's purpose is to take care of the finer details of persistence and data querying for us. As the caller, we only care about fetching and persisting data. The `Repo` module takes care of the underlying database adapter communication, connection pooling, and error translation for database constraint violations. Let's head back over to IEx with `iex -S mix`, and insert a couple of users into the database. ``` iex> alias Hello.{Repo, User} [Hello.Repo, Hello.User] iex> Repo.insert(%User{email: "user1@example.com"}) [debug] QUERY OK db=6.5ms queue=0.5ms idle=1358.3ms INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user1@example.com", ~N[2021-02-25 01:58:55], ~N[2021-02-25 01:58:55]] {:ok, %Hello.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, bio: nil, email: "user1@example.com", id: 1, inserted_at: ~N[2021-02-25 01:58:55], name: nil, number_of_pets: nil, updated_at: ~N[2021-02-25 01:58:55] }} iex> Repo.insert(%User{email: "user2@example.com"}) [debug] QUERY OK db=1.3ms idle=1402.7ms INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user2@example.com", ~N[2021-02-25 02:03:28], ~N[2021-02-25 02:03:28]] {:ok, %Hello.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, bio: nil, email: "user2@example.com", id: 2, inserted_at: ~N[2021-02-25 02:03:28], name: nil, number_of_pets: nil, updated_at: ~N[2021-02-25 02:03:28] }} ``` We started by aliasing our `User` and `Repo` modules for easy access. Next, we called [`Repo.insert/2`](../ecto/3.10.1/Ecto.Repo.html#c:insert/2) with a User struct. Since we are in the `dev` environment, we can see the debug logs for the query our repository performed when inserting the underlying `%User{}` data. We received a two-element tuple back with `{:ok, %User{}}`, which lets us know the insertion was successful. We could also insert a user by passing a changeset to [`Repo.insert/2`](../ecto/3.10.1/Ecto.Repo.html#c:insert/2). If the changeset is valid, the repository will use an optimized database query to insert the record, and return a two-element tuple back, as above. If the changeset is not valid, we receive a two-element tuple consisting of `:error` plus the invalid changeset. With a couple of users inserted, let's fetch them back out of the repo. ``` iex> Repo.all(User) [debug] QUERY OK source="users" db=5.8ms queue=1.4ms idle=1672.0ms SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."inserted_at", u0."updated_at" FROM "users" AS u0 [] [ %Hello.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, bio: nil, email: "user1@example.com", id: 1, inserted_at: ~N[2021-02-25 01:58:55], name: nil, number_of_pets: nil, updated_at: ~N[2021-02-25 01:58:55] }, %Hello.User{ __meta__: #Ecto.Schema.Metadata<:loaded, "users">, bio: nil, email: "user2@example.com", id: 2, inserted_at: ~N[2021-02-25 02:03:28], name: nil, number_of_pets: nil, updated_at: ~N[2021-02-25 02:03:28] } ] ``` That was easy! `Repo.all/1` takes a data source, our `User` schema in this case, and translates that to an underlying SQL query against our database. After it fetches the data, the Repo then uses our Ecto schema to map the database values back into Elixir data structures according to our `User` schema. We're not just limited to basic querying – Ecto includes a full-fledged query DSL for advanced SQL generation. In addition to a natural Elixir DSL, Ecto's query engine gives us multiple great features, such as SQL injection protection and compile-time optimization of queries. Let's try it out. ``` iex> import Ecto.Query Ecto.Query iex> Repo.all(from u in User, select: u.email) [debug] QUERY OK source="users" db=0.8ms queue=0.9ms idle=1634.0ms SELECT u0."email" FROM "users" AS u0 [] ["user1@example.com", "user2@example.com"] ``` First, we imported \[[`Ecto.Query`](../ecto/3.10.1/Ecto.Query.html)], which imports the [`from/2`](../ecto/3.10.1/Ecto.Query.html#from/2) macro of Ecto's Query DSL. Next, we built a query which selects all the email addresses in our users table. Let's try another example. ``` iex> Repo.one(from u in User, where: ilike(u.email, "%1%"), select: count(u.id)) [debug] QUERY OK source="users" db=1.6ms SELECT count(u0."id") FROM "users" AS u0 WHERE (u0."email" ILIKE '%1%') [] 1 ``` Now we're starting to get a taste of Ecto's rich querying capabilities. We used [`Repo.one/2`](../ecto/3.10.1/Ecto.Repo.html#c:one/2) to fetch the count of all users with an email address containing `1`, and received the expected count in return. This just scratches the surface of Ecto's query interface, and much more is supported such as sub-querying, interval queries, and advanced select statements. For example, let's build a query to fetch a map of all user id's to their email addresses. ``` iex> Repo.all(from u in User, select: %{u.id => u.email}) [debug] QUERY OK source="users" db=0.9ms SELECT u0."id", u0."email" FROM "users" AS u0 [] [ %{1 => "user1@example.com"}, %{2 => "user2@example.com"} ] ``` That little query packed a big punch. It both fetched all user emails from the database and efficiently built a map of the results in one go. You should browse the [Ecto.Query documentation](https://hexdocs.pm/ecto/Ecto.Query.html#content) to see the breadth of supported query features. In addition to inserts, we can also perform updates and deletes with [`Repo.update/2`](../ecto/3.10.1/Ecto.Repo.html#c:update/2) and [`Repo.delete/2`](../ecto/3.10.1/Ecto.Repo.html#c:delete/2) to update or delete a single schema. Ecto also supports bulk persistence with the [`Repo.insert_all/3`](../ecto/3.10.1/Ecto.Repo.html#c:insert_all/3), [`Repo.update_all/3`](../ecto/3.10.1/Ecto.Repo.html#c:update_all/3), and [`Repo.delete_all/2`](../ecto/3.10.1/Ecto.Repo.html#c:delete_all/2) functions. There is quite a bit more that Ecto can do and we've only barely scratched the surface. With a solid Ecto foundation in place, we're now ready to continue building our app and integrate the web-facing application with our backend persistence. Along the way, we'll expand our Ecto knowledge and learn how to properly isolate our web interface from the underlying details of our system. Please take a look at the [Ecto documentation](../ecto/index.html) for the rest of the story. In our [contexts guide](contexts.html), we'll find out how to wrap up our Ecto access and business logic behind modules that group related functionality. We'll see how Phoenix helps us design maintainable applications, and we'll find out about other neat Ecto features along the way. ## [](ecto.html#using-other-databases)Using other databases Phoenix applications are configured to use PostgreSQL by default, but what if we want to use another database, such as MySQL? In this section, we'll walk through changing that default whether we are about to create a new application, or whether we have an existing one configured for PostgreSQL. If we are about to create a new application, configuring our application to use MySQL is easy. We can simply pass the `--database mysql` flag to `phx.new` and everything will be configured correctly. ``` $ mix phx.new hello_phoenix --database mysql ``` This will set up all the correct dependencies and configuration for us automatically. Once we install those dependencies with [`mix deps.get`](../mix/Mix.Tasks.Deps.Get.html), we'll be ready to begin working with Ecto in our application. If we have an existing application, all we need to do is switch adapters and make some small configuration changes. To switch adapters, we need to remove the Postgrex dependency and add a new one for MyXQL instead. Let's open up our `mix.exs` file and do that now. ``` defmodule HelloPhoenix.MixProject do use Mix.Project . . . # Specifies your project dependencies. # # Type `mix help deps` for examples and options. defp deps do [ {:phoenix, "~> 1.4.0"}, {:phoenix_ecto, "~> 4.4"}, {:ecto_sql, "~> 3.10"}, {:myxql, ">= 0.0.0"}, ... ] end end ``` Next, we need to configure our adapter to use the default MySQL credentials by updating `config/dev.exs`: ``` config :hello_phoenix, HelloPhoenix.Repo, username: "root", password: "", database: "hello_phoenix_dev" ``` If we have an existing configuration block for our `HelloPhoenix.Repo`, we can simply change the values to match our new ones. You also need to configure the correct values in the `config/test.exs` and `config/runtime.exs` (formerly `config/prod.secret.exs`) files as well. The last change is to open up `lib/hello_phoenix/repo.ex` and make sure to set the `:adapter` to [`Ecto.Adapters.MyXQL`](../ecto_sql/3.10.1/Ecto.Adapters.MyXQL.html). Now all we need to do is fetch our new dependency, and we'll be ready to go. ``` $ mix deps.get ``` With our new adapter installed and configured, we're ready to create our database. ``` $ mix ecto.create ``` The database for HelloPhoenix.Repo has been created. We're also ready to run any migrations, or do anything else with Ecto that we may choose. ``` $ mix ecto.migrate [info] == Running HelloPhoenix.Repo.Migrations.CreateUser.change/0 forward [info] create table users [info] == Migrated in 0.2s ``` ## [](ecto.html#other-options)Other options While Phoenix uses the [`Ecto`](../ecto/3.10.1/Ecto.html) project to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](https://www.erlang.org/doc/man/ets.html) – available in Ecto via [`etso`](../etso/index.html) – and [DETS](https://www.erlang.org/doc/man/dets.html) are key-value data stores built into [OTP](https://www.erlang.org/doc/). OTP also provides a relational database called [Mnesia](https://www.erlang.org/doc/man/mnesia.html) with its own query language called QLC. Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores. The data world is your oyster, but we won't be covering these options in these guides. [← Previous Page Components and HEEx](components.html) [Next Page → Contexts](contexts.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/ecto.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/file_uploads.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/howto/file_uploads.md#L1 "View Source") File Uploads One common task for web applications is uploading files. These files might be images, videos, PDFs, or files of any other type. In order to upload files through an HTML interface, we need a `file` input tag in a multipart form. #### Looking for the LiveView Uploads guide? This guide explains multipart HTTP file uploads via [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html). For more information about LiveView file uploads, including direct-to-cloud external uploads on the client, refer to the [LiveView Uploads guide](../phoenix_live_view/uploads.html). Plug provides a [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html) struct to hold the data from the `file` input. A [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html) struct will automatically appear in your request parameters if a user has selected a file when they submit the form. In this guide you will do the following: 1. Configure a multipart form 2. Add a file input element to the form 3. Verify your upload params 4. Manage your uploaded files In the [`Contexts guide`](contexts.html), we generated an HTML resource for products. We can reuse the form we generated there in order to demonstrate how file uploads work in Phoenix. Please refer to that guide for instructions on generating the product resource you will be using here. ### [](file_uploads.html#configure-a-multipart-form)Configure a multipart form The first thing you need to do is change your form into a multipart form. The `HelloWeb.CoreComponents` `simple_form/1` component accepts a `multipart` attribute where you can specify this. Here is the form from `lib/hello_web/controllers/product_html/product_form.html.heex` with that change in place: ``` <.simple_form :let={f} for={@changeset} action={@action} multipart> . . . ``` ### [](file_uploads.html#add-a-file-input)Add a file input Once you have a multipart form, you need a `file` input. Here's how you would do that, also in `product_form.html.heex`: ``` . . . <.input field={f[:photo]} type="file" label="Photo" /> <:actions> <.button>Save Product</.button> </:actions> </.simple_form> ``` When rendered, here is the HTML for the default `HelloWeb.CoreComponents` `input/1` component: ``` <div> <label for="product_photo" class="block text-sm...">Photo</label> <input type="file" name="product[photo]" id="product_photo" class="mt-2 block w-full..."> </div> ``` Note the `name` attribute of your `file` input. This will create the `"photo"` key in the `product_params` map which will be available in your controller action. This is all from the form side. Now when users submit the form, a `POST` request will route to your `HelloWeb.ProductController` `create/2` action. #### Should I add photo to my Ecto schema? The photo input does not need to be part of your schema for it to come across in the `product_params`. If you want to persist any properties of the photo in a database, however, you would need to add it to your `Hello.Product` schema. ### [](file_uploads.html#verify-your-upload-params)Verify your upload params Since you generated an HTML resource, you can now start your server with [`mix phx.server`](Mix.Tasks.Phx.Server.html), visit [http://localhost:4000/products/new](http://localhost:4000/products/new), and create a new product with a photo. Before you begin, add `IO.inspect product_params` to the top of your `ProductController.create/2` action in `lib/hello_web/controllers/product_controller.ex`. This will show the `product_params` in your development log so you can get a better sense of what's happening. ``` . . . def create(conn, %{"product" => product_params}) do IO.inspect product_params . . . ``` When you do that, this is what your `product_params` will output in the log: ``` %{"title" => "Metaprogramming Elixir", "description" => "Write Less Code, Get More Done (and Have Fun!)", "price" => "15.000000", "views" => "0", "photo" => %Plug.Upload{content_type: "image/png", filename: "meta-cover.png", path: "/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/multipart-558399-917557-1"}} ``` You have a `"photo"` key which maps to the pre-populated [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html) struct representing your uploaded photo. To make this easier to read, focus on the struct itself: ``` %Plug.Upload{content_type: "image/png", filename: "meta-cover.png", path: "/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/multipart-558399-917557-1"} ``` [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html) provides the file's content type, original filename, and path to the temporary file which Plug created for you. In this case, `"/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/"` is the directory created by Plug in which to put uploaded files. The directory will persist across requests. `"multipart-558399-917557-1"` is the name Plug gave to your uploaded file. If you had multiple `file` inputs and if the user selected photos for all of them, you would have multiple files scattered in temporary directories. Plug will make sure all the filenames are unique. #### Plug.Upload files are temporary Plug removes uploads from its directory as the request completes. If you need to do anything with this file, you need to do it before then (or [give it away](../plug/1.15.3/Plug.Upload.html#give_away/3), but that is outside the scope of this guide). ### [](file_uploads.html#manage-your-uploaded-files)Manage your uploaded files Once you have the [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html) struct available in your controller, you can perform any operation on it you want. For example, you may want to do one or more of the following: - Check to make sure the file exists with [`File.exists?/1`](../elixir/File.html#exists?/1) - Copy the file somewhere else on the filesystem with [`File.cp/2`](../elixir/File.html#cp/2) - Give the file away to another Elixir process with [`Plug.Upload.give_away/3`](../plug/1.15.3/Plug.Upload.html#give_away/3) - Send it to S3 with an external library - Send it back to the client with [`Plug.Conn.send_file/5`](../plug/1.15.3/Plug.Conn.html#send_file/5) In a production system, you may want to copy the file to a root directory, such as `/media`. When doing so, it is important to guarantee the names are unique. For instance, if you are allowing users to upload product cover images, you could use the product id to generate a unique name: ``` if upload = product_params["photo"] do extension = Path.extname(upload.filename) File.cp(upload.path, "/media/#{product.id}-cover#{extension}") end ``` Then a [`Plug.Static`](../plug/1.15.3/Plug.Static.html) plug could be added in your `lib/my_app_web/endpoint.ex` to serve the files at `"/media"`: ``` plug Plug.Static, at: "/uploads", from: "/media" ``` The uploaded file can now be accessed from your browsers using a path such as `"/uploads/1-cover.jpg"`. In practice, there are other concerns you want to handle when uploading files, such validating extensions, encoding names, and so on. Many times, using a library that already handles such cases is preferred. Finally, notice that when there is no data from the `file` input, you get neither the `"photo"` key nor a [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html) struct. Here are the `product_params` from the log. ``` %{"title" => "Metaprogramming Elixir", "description" => "Write Less Code, Get More Done (and Have Fun!)", "price" => "15.000000", "views" => "0"} ``` ## [](file_uploads.html#configuring-upload-limits)Configuring upload limits The conversion from the data being sent by the form to an actual [`Plug.Upload`](../plug/1.15.3/Plug.Upload.html) is done by the [`Plug.Parsers`](../plug/1.15.3/Plug.Parsers.html) plug which you can find inside `HelloWeb.Endpoint`: ``` # lib/hello_web/endpoint.ex plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library() ``` Besides the options above, [`Plug.Parsers`](../plug/1.15.3/Plug.Parsers.html) accepts other options to control data upload: - `:length` - sets the max body length to read, defaults to `8_000_000` bytes - `:read_length` - set the amount of bytes to read at one time, defaults to `1_000_000` bytes - `:read_timeout` - set the timeout for each chunk received, defaults to `15_000` ms The first option configures the maximum data allowed. The remaining ones configure how much data we expect to read and its frequency. If the client cannot push data fast enough, the connection will be terminated. Phoenix ships with reasonable defaults but you may want to customize it under special circumstances, for example, if you are expecting really slow clients to send large chunks of data. It is also worth pointing out those limits are important as a security mechanism. For example, if you don't set a limit for data upload, attackers could open up thousands of connections to your application and send one byte every 2 minutes, which would take very long to complete while using up all connections to your server. The limits above expect at least a reasonable amount of progress, making attackers' lives a bit harder. [← Previous Page Custom Error Pages](custom_error_pages.html) [Next Page → Using SSL](using_ssl.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/howto/file_uploads.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/fly.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/deployment/fly.md#L1 "View Source") Deploying on Fly.io Fly.io maintains their own guide for Elixir/Phoenix here: [Fly.io/docs/elixir/getting-started/](https://fly.io/docs/elixir/getting-started/) we will keep this guide up but for the latest and greatest check with them! ## [](fly.html#what-we-ll-need)What we'll need The only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](up_and_running.html). You can just: ``` $ mix phx.new my_app ``` ## [](fly.html#goals)Goals The main goal for this guide is to get a Phoenix application running on [Fly.io](https://fly.io). ## [](fly.html#sections)Sections Let's separate this process into a few steps, so we can keep track of where we are. - Install the Fly.io CLI - Sign up for Fly.io - Deploy the app to Fly.io - Extra Fly.io tips - Helpful Fly.io resources ## [](fly.html#installing-the-fly-io-cli)Installing the Fly.io CLI Follow the instructions [here](https://fly.io/docs/getting-started/installing-flyctl/) to install Flyctl, the command-line interface for the Fly.io platform. ## [](fly.html#sign-up-for-fly-io)Sign up for Fly.io We can [sign up for an account](https://fly.io/docs/getting-started/log-in-to-fly/) using the CLI. ``` $ fly auth signup ``` Or sign in. ``` $ flyctl auth login ``` Fly has a [free tier](https://fly.io/docs/about/pricing/) for most applications. A credit card is required when setting up an account to help prevent abuse. See the [pricing](https://fly.io/docs/about/pricing/) page for more details. ## [](fly.html#deploy-the-app-to-fly-io)Deploy the app to Fly.io To tell Fly about your application, run `fly launch` in the directory with your source code. This creates and configures a Fly.io app. ``` $ fly launch ``` This scans your source, detects the Phoenix project, and runs `mix phx.gen.release --docker` for you! This creates a Dockerfile for you. The `fly launch` command walks you through a few questions. - You can name the app or have it generate a random name for you. - Choose an organization (defaults to `personal`). Organizations are a way of sharing applications and resources between Fly.io users. - Choose a region to deploy to. Defaults to the nearest Fly.io region. You can check out the [complete list of regions here](https://fly.io/docs/reference/regions/). - Sets up a Postgres DB for you. - Builds the Dockerfile. - Deploys your application! The `fly launch` command also created a `fly.toml` file for you. This is where you can set ENV values and other config. ### [](fly.html#storing-secrets-on-fly-io)Storing secrets on Fly.io You may also have some secrets you'd like to set on your app. Use [`fly secrets`](https://fly.io/docs/reference/secrets/#setting-secrets) to configure those. ``` $ fly secrets set MY_SECRET_KEY=my_secret_value ``` ### [](fly.html#deploying-again)Deploying again When you want to deploy changes to your application, use `fly deploy`. ``` $ fly deploy ``` Note: On Apple Silicon (M1) computers, docker runs cross-platform builds using qemu which might not always work. If you get a segmentation fault error like the following: ``` => [build 7/17] RUN mix deps.get --only => => # qemu: uncaught target signal 11 (Segmentation fault) - core dumped ``` You can use fly's remote builder by adding the `--remote-only` flag: ``` $ fly deploy --remote-only ``` You can always check on the status of a deploy ``` $ fly status ``` Check your app logs ``` $ fly logs ``` If everything looks good, open your app on Fly ``` $ fly open ``` ## [](fly.html#extra-fly-io-tips)Extra Fly.io tips ### [](fly.html#getting-an-iex-shell-into-a-running-node)Getting an IEx shell into a running node Elixir supports getting a IEx shell into a running production node. There are a couple prerequisites, we first need to establish an [SSH Shell](https://fly.io/docs/flyctl/ssh/) to our machine on Fly.io. This step sets up a root certificate for your account and then issues a certificate. ``` $ fly ssh issue --agent ``` With SSH configured, let's open a console. ``` $ fly ssh console Connecting to my-app-1234.internal... complete / # ``` If all has gone smoothly, then you have a shell into the machine! Now we just need to launch our remote IEx shell. The deployment Dockerfile was configured to pull our application into `/app`. So the command for an app named `my_app` looks like this: ``` $ app/bin/my_app remote Erlang/OTP 23 [erts-11.2.1] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1] Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help) iex(my_app@fdaa:0:1da8:a7b:ac4:b204:7e29:2)1> ``` Now we have a running IEx shell into our node! You can safely disconnect using CTRL+C, CTRL+C. ### [](fly.html#clustering-your-application)Clustering your application Elixir and the BEAM have the incredible ability to be clustered together and pass messages seamlessly between nodes. This portion of the guide walks you through clustering your Elixir application. There are 2 parts to getting clustering quickly setup on Fly.io. - Installing and using `libcluster` - Scaling the application to multiple instances #### Adding `libcluster` The widely adopted library [libcluster](https://github.com/bitwalker/libcluster) helps here. There are multiple strategies that `libcluster` can use to find and connect with other nodes. The strategy we'll use on Fly.io is `DNSPoll`. After installing `libcluster`, add it to the application like this: ``` defmodule MyApp.Application do use Application def start(_type, _args) do topologies = Application.get_env(:libcluster, :topologies) || [] children = [ # ... # setup for clustering {Cluster.Supervisor, [topologies, [name: MyApp.ClusterSupervisor]]} ] # ... end # ... end ``` Our next step is to add the `topologies` configuration to `config/runtime.exs`. ``` app_name = System.get_env("FLY_APP_NAME") || raise "FLY_APP_NAME not available" config :libcluster, topologies: [ fly6pn: [ strategy: Cluster.Strategy.DNSPoll, config: [ polling_interval: 5_000, query: "#{app_name}.internal", node_basename: app_name ] ] ] ``` This configures `libcluster` to use the `DNSPoll` strategy and look for other deployed apps using the `$FLY_APP_NAME` on the `.internal` private network. #### Controlling the name for our node We need to control the naming of our Elixir nodes. To help them connect up, we'll name them using this pattern: `your-fly-app-name@the.ipv6.address.on.fly`. To do this, we'll generate the release config. ``` $ mix release.init ``` Then edit the generated `rel/env.sh.eex` file and add the following lines: ``` ip=$(grep fly-local-6pn /etc/hosts | cut -f 1) export RELEASE_DISTRIBUTION=name export RELEASE_NODE=$FLY_APP_NAME@$ip ``` After making the change, deploy your app! ``` $ fly deploy ``` For our app to be clustered, we have to have multiple instances. Next we'll add an additional node instance. #### Running multiple instances There are two ways to run multiple instances. 1. Scale our application to have multiple instances in one region. 2. Add an instance to another region (multiple regions). Let's first start with a baseline of our single deployment. ``` $ fly status ... Instances ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED f9014bf7 26 sea run running 1 total, 1 passing 0 1h8m ago ``` #### Scaling in a single region Let's scale up to 2 instances in our current region. ``` $ fly scale count 2 Count changed to 2 ``` Checking the status, we can see what happened. ``` $ fly status ... Instances ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED eb4119d3 27 sea run running 1 total, 1 passing 0 39s ago f9014bf7 27 sea run running 1 total, 1 passing 0 1h13m ago ``` We now have two instances in the same region. Let's make sure they are clustered together. We can check the logs: ``` $ fly logs ... app[eb4119d3] sea [info] 21:50:21.924 [info] [libcluster:fly6pn] connected to :"my-app-1234@fdaa:0:1da8:a7b:ac2:f901:4bf7:2" ... ``` But that's not as rewarding as seeing it from inside a node. From an IEx shell, we can ask the node we're connected to, what other nodes it can see. ``` $ fly ssh console -C "/app/bin/my_app remote" ``` ``` iex(my-app-1234@fdaa:0:1da8:a7b:ac2:f901:4bf7:2)1> Node.list [:"my-app-1234@fdaa:0:1da8:a7b:ac4:eb41:19d3:2"] ``` The IEx prompt is included to help show the IP address of the node we are connected to. Then getting the `Node.list` returns the other node. Our two instances are connected and clustered! #### Scaling to multiple regions Fly makes it easy to deploy instances closer to your users. Through the magic of DNS, users are directed to the nearest region where your application is located. You can read more about [Fly.io regions here](https://fly.io/docs/reference/regions/). Starting back from our baseline of a single instance running in `sea` which is Seattle, Washington (US), let's add the region `ewr` which is Parsippany, NJ (US). This puts an instance on both coasts of the US. ``` $ fly regions add ewr Region Pool: ewr sea Backup Region: iad lax sjc vin ``` Looking at the status shows that we're only in 1 region because our count is set to 1. ``` $ fly status ... Instances ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED cdf6c422 29 sea run running 1 total, 1 passing 0 58s ago ``` Let's add a 2nd instance and see it deploy to `ewr`. ``` $ fly scale count 2 Count changed to 2 ``` Now the status shows we have two instances spread across 2 regions! ``` $ fly status ... Instances ID VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED 0a8e6666 30 ewr run running 1 total, 1 passing 0 16s ago cdf6c422 30 sea run running 1 total, 1 passing 0 6m47s ago ``` Let's ensure they are clustered together. ``` $ fly ssh console -C "/app/bin/my_app remote" ``` ``` iex(my-app-1234@fdaa:0:1da8:a7b:ac2:cdf6:c422:2)1> Node.list [:"my-app-1234@fdaa:0:1da8:a7b:ab2:a8e:6666:2"] ``` We have two instances of our application deployed to the West and East coasts of the North American continent and they are clustered together! Our users will automatically be directed to the server nearest them. The Fly.io platform has built-in distribution support making it easy to cluster distributed Elixir nodes in multiple regions. ## [](fly.html#helpful-fly-io-resources)Helpful Fly.io resources Open the Dashboard for your account ``` $ fly dashboard ``` Deploy your application ``` $ fly deploy ``` Show the status of your deployed application ``` $ fly status ``` Access and tail the logs ``` $ fly logs ``` Scaling your application up or down ``` $ fly scale count 2 ``` Refer to the [Fly.io Elixir documentation](https://fly.io/docs/getting-started/elixir) for additional information. [Working with Fly.io applications](https://fly.io/docs/getting-started/working-with-fly-apps/) covers things like: - Status and logs - Custom domains - Certificates ## [](fly.html#troubleshooting)Troubleshooting See [Troubleshooting](https://fly.io/docs/getting-started/troubleshooting/) and [Elixir Troubleshooting](https://fly.io/docs/elixir/the-basics/troubleshooting/) Visit the [Fly.io Community](https://community.fly.io/) to find solutions and ask questions. [← Previous Page Deploying on Gigalixir](gigalixir.html) [Next Page → Deploying on Heroku](heroku.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/deployment/fly.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/gigalixir.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/deployment/gigalixir.md#L1 "View Source") Deploying on Gigalixir ## [](gigalixir.html#what-we-ll-need)What we'll need The only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](up_and_running.html). ## [](gigalixir.html#goals)Goals Our main goal for this guide is to get a Phoenix application running on Gigalixir. ## [](gigalixir.html#steps)Steps Let's separate this process into a few steps, so we can keep track of where we are. - Initialize Git repository - Install the Gigalixir CLI - Sign up for Gigalixir - Create and set up Gigalixir application - Provision a database - Make our project ready for Gigalixir - Deploy time! - Useful Gigalixir commands ## [](gigalixir.html#initializing-git-repository)Initializing Git repository If you haven't already, we'll need to commit our files to git. We can do so by running the following commands in our project directory: ``` $ git init $ git add . $ git commit -m "Initial commit" ``` ## [](gigalixir.html#installing-the-gigalixir-cli)Installing the Gigalixir CLI Follow the instructions [here](https://gigalixir.readthedocs.io/en/latest/getting-started-guide.html#install-the-command-line-interface) to install the command-line interface for your platform. ## [](gigalixir.html#signing-up-for-gigalixir)Signing up for Gigalixir We can sign up for an account at [gigalixir.com](https://www.gigalixir.com) or with the CLI. Let's use the CLI. ``` $ gigalixir signup ``` Gigalixir’s free tier does not require a credit card and comes with 1 app instance and 1 PostgreSQL database for free, but please consider upgrading to a paid plan if you are running a production application. Next, let's login ``` $ gigalixir login ``` And verify ``` $ gigalixir account ``` ## [](gigalixir.html#creating-and-setting-up-our-gigalixir-application)Creating and setting up our Gigalixir application There are three different ways to deploy a Phoenix app on Gigalixir: with mix, with Elixir's releases, or with Distillery. In this guide, we'll be using Mix because it is the easiest to get up and running, but you won't be able to connect a remote observer or hot upgrade. For more information, see [Mix vs Distillery vs Elixir Releases](https://gigalixir.readthedocs.io/en/latest/modify-app/index.html#mix-vs-distillery-vs-elixir-releases). If you want to deploy with another method, follow the [Getting Started Guide](https://gigalixir.readthedocs.io/en/latest/getting-started-guide.html). ### [](gigalixir.html#creating-a-gigalixir-application)Creating a Gigalixir application Let's create a Gigalixir application ``` $ gigalixir create -n "your_app_name" ``` Note: the app name cannot be changed afterwards. A random name is used if you do not provide one. Verify the app was created ``` $ gigalixir apps ``` Verify that a git remote was created ``` $ git remote -v ``` ### [](gigalixir.html#specifying-versions)Specifying versions The buildpacks we use default to Elixir, Erlang, and Node.js versions that are quite old and it's generally a good idea to run the same version in production as you do in development, so let's do that. ``` $ echo 'elixir_version=1.14.3' > elixir_buildpack.config $ echo 'erlang_version=24.3' >> elixir_buildpack.config $ echo 'node_version=12.16.3' > phoenix_static_buildpack.config ``` Phoenix v1.6 uses `esbuild` to compile your assets, but all Gigalixir images come with `npm`, so we will configure `npm` directly to deploy our assets. Add a `assets/package.json` file if you don't have any with the following: ``` { "scripts": { "deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild*" } } ``` Finally, don't forget to commit: ``` $ git add elixir_buildpack.config phoenix_static_buildpack.config assets/package.json $ git commit -m "Set Elixir, Erlang, and Node version" ``` ## [](gigalixir.html#making-our-project-ready-for-gigalixir)Making our Project ready for Gigalixir There's nothing we need to do to get our app running on Gigalixir, but for a production app, you probably want to enforce SSL. To do that, see [Force SSL](using_ssl.html#force-ssl) You may also want to use SSL for your database connection. For that, uncomment the line `ssl: true` in your `Repo` config. ## [](gigalixir.html#provisioning-a-database)Provisioning a database Let's provision a database for our app ``` $ gigalixir pg:create --free ``` Verify the database was created ``` $ gigalixir pg ``` Verify that a `DATABASE_URL` and `POOL_SIZE` were created ``` $ gigalixir config ``` ## [](gigalixir.html#deploy-time)Deploy Time! Our project is now ready to be deployed on Gigalixir. ``` $ git push gigalixir ``` Check the status of your deploy and wait until the app is `Healthy` ``` $ gigalixir ps ``` Run migrations ``` $ gigalixir run mix ecto.migrate ``` Check your app logs ``` $ gigalixir logs ``` If everything looks good, let's take a look at your app running on Gigalixir ``` $ gigalixir open ``` ## [](gigalixir.html#useful-gigalixir-commands)Useful Gigalixir Commands Open a remote console ``` $ gigalixir account:ssh_keys:add "$(cat ~/.ssh/id_rsa.pub)" $ gigalixir ps:remote_console ``` To open a remote observer, see [Remote Observer](https://gigalixir.readthedocs.io/en/latest/runtime.html#how-to-launch-a-remote-observer) To set up clustering, see [Clustering Nodes](https://gigalixir.readthedocs.io/en/latest/cluster.html) To hot upgrade, see [Hot Upgrades](https://gigalixir.readthedocs.io/en/latest/deploy.html#how-to-hot-upgrade-an-app) For custom domains, scaling, jobs and other features, see the [Gigalixir Documentation](https://gigalixir.readthedocs.io/) ## [](gigalixir.html#troubleshooting)Troubleshooting See [Troubleshooting](https://gigalixir.readthedocs.io/en/latest/troubleshooting.html) Also, don't hesitate to email [help@gigalixir.com](mailto:help@gigalixir.com) or [request an invitation](https://elixir-lang.slack.com/join/shared_invite/zt-1f13hz7mb-N4KGjF523ONLCcHfb8jYgA#/shared-invite/email) and join the #gigalixir channel on [Slack](https://elixir-lang.slack.com). [← Previous Page Deploying with Releases](releases.html) [Next Page → Deploying on Fly.io](fly.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/deployment/gigalixir.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/heroku.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/deployment/heroku.md#L1 "View Source") Deploying on Heroku ## [](heroku.html#what-we-ll-need)What we'll need The only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](up_and_running.html). ## [](heroku.html#goals)Goals Our main goal for this guide is to get a Phoenix application running on Heroku. ## [](heroku.html#limitations)Limitations Heroku is a great platform and Elixir performs well on it. However, you may run into limitations if you plan to leverage advanced features provided by Elixir and Phoenix, such as: - Connections are limited. - Heroku [limits the number of simultaneous connections](https://devcenter.heroku.com/articles/http-routing#request-concurrency) as well as the [duration of each connection](https://devcenter.heroku.com/articles/limits#http-timeouts). It is common to use Elixir for real-time apps which need lots of concurrent, persistent connections, and Phoenix is capable of [handling over 2 million connections on a single server](https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections). - Distributed clustering is not possible. - Heroku [firewalls dynos off from one another](https://devcenter.heroku.com/articles/dynos#networking). This means things like [distributed Phoenix channels](https://dockyard.com/blog/2016/01/28/running-elixir-and-phoenix-projects-on-a-cluster-of-nodes) and [distributed tasks](../elixir/distributed-tasks.html) will need to rely on something like Redis instead of Elixir's built-in distribution. - In-memory state such as those in [Agents](../elixir/agents.html), [GenServers](../elixir/genservers.html), and [ETS](../elixir/erlang-term-storage.html) will be lost every 24 hours. - Heroku [restarts dynos](https://devcenter.heroku.com/articles/dynos#restarting) every 24 hours regardless of whether the node is healthy. - [The built-in observer](../elixir/debugging.html#observer) can't be used with Heroku. - Heroku does allow for connection into your dyno, but you won't be able to use the observer to watch the state of your dyno. If you are just getting started, or you don't expect to use the features above, Heroku should be enough for your needs. For instance, if you are migrating an existing application running on Heroku to Phoenix, keeping a similar set of features, Elixir will perform just as well or even better than your current stack. If you want a platform-as-a-service without these limitations, try [Gigalixir](gigalixir.html). If you would rather deploy to a cloud platform, such as EC2, Google Cloud, etc, consider using [`mix release`](../mix/Mix.Tasks.Release.html). ## [](heroku.html#steps)Steps Let's separate this process into a few steps, so we can keep track of where we are. - Initialize Git repository - Sign up for Heroku - Install the Heroku Toolbelt - Create and set up Heroku application - Make our project ready for Heroku - Deploy time! - Useful Heroku commands ## [](heroku.html#initializing-git-repository)Initializing Git repository [Git](https://git-scm.com/) is a popular decentralized revision control system and is also used to deploy apps to Heroku. Before we can push to Heroku, we'll need to initialize a local Git repository and commit our files to it. We can do so by running the following commands in our project directory: ``` $ git init $ git add . $ git commit -m "Initial commit" ``` Heroku offers some great information on how it is using Git [here](https://devcenter.heroku.com/articles/git#prerequisites-install-git-and-the-heroku-cli). ## [](heroku.html#signing-up-for-heroku)Signing up for Heroku Signing up to Heroku is very simple, just head over to [https://signup.heroku.com/](https://signup.heroku.com/) and fill in the form. The Free plan will give us one web [dyno](https://devcenter.heroku.com/articles/dynos) and one worker dyno, as well as a PostgreSQL and Redis instance for free. These are meant to be used for testing and development, and come with some limitations. In order to run a production application, please consider upgrading to a paid plan. ## [](heroku.html#installing-the-heroku-toolbelt)Installing the Heroku Toolbelt Once we have signed up, we can download the correct version of the Heroku Toolbelt for our system [here](https://toolbelt.heroku.com/). The Heroku CLI, part of the Toolbelt, is useful to create Heroku applications, list currently running dynos for an existing application, tail logs or run one-off commands (mix tasks for instance). ## [](heroku.html#create-and-set-up-heroku-application)Create and Set Up Heroku Application There are two different ways to deploy a Phoenix app on Heroku. We could use Heroku buildpacks or their container stack. The difference between these two approaches is in how we tell Heroku to treat our build. In buildpack case, we need to update our apps configuration on Heroku to use Phoenix/Elixir specific buildpacks. On container approach, we have more control on how we want to set up our app, and we can define our container image using `Dockerfile` and `heroku.yml`. This section will explore the buildpack approach. In order to use Dockerfile, it is often recommended to convert our app to use releases, which we will describe later on. ### [](heroku.html#create-application)Create Application A [buildpack](https://devcenter.heroku.com/articles/buildpacks) is a convenient way of packaging framework and/or runtime support. Phoenix requires 2 buildpacks to run on Heroku, the first adds basic Elixir support and the second adds Phoenix specific commands. With the Toolbelt installed, let's create the Heroku application. We will do so using the latest available version of the [Elixir buildpack](https://github.com/HashNuke/heroku-buildpack-elixir): ``` $ heroku create --buildpack hashnuke/elixir Creating app... done, ⬢ mysterious-meadow-6277 Setting buildpack to hashnuke/elixir... done https://mysterious-meadow-6277.herokuapp.com/ | https://git.heroku.com/mysterious-meadow-6277.git ``` > Note: the first time we use a Heroku command, it may prompt us to log in. If this happens, just enter the email and password you specified during signup. > Note: the name of the Heroku application is the random string after "Creating" in the output above (mysterious-meadow-6277). This will be unique, so expect to see a different name from "mysterious-meadow-6277". > Note: the URL in the output is the URL to our application. If we open it in our browser now, we will get the default Heroku welcome page. > Note: if we hadn't initialized our Git repository before we ran the `heroku create` command, we wouldn't have our Heroku remote repository properly set up at this point. We can set that up manually by running: `heroku git:remote -a [our-app-name].` The buildpack uses a predefined Elixir and Erlang version, but to avoid surprises when deploying, it is best to explicitly list the Elixir and Erlang version we want in production to be the same we are using during development or in your continuous integration servers. This is done by creating a config file named `elixir_buildpack.config` in the root directory of your project with your target version of Elixir and Erlang: ``` # Elixir version elixir_version=1.14.0 # Erlang version # https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions erlang_version=24.3 # Invoke assets.deploy defined in your mix.exs to deploy assets with esbuild # Note we nuke the esbuild executable from the image hook_post_compile="eval mix assets.deploy && rm -f _build/esbuild*" ``` Finally, let's tell the build pack how to start our webserver. Create a file named `Procfile` at the root of your project: ``` web: mix phx.server ``` ### [](heroku.html#optional-node-npm-and-the-phoenix-static-buildpack)Optional: Node, npm, and the Phoenix Static buildpack By default, Phoenix uses `esbuild` and manages all assets for you. However, if you are using `node` and `npm`, you will need to install the [Phoenix Static buildpack](https://github.com/gjaldon/heroku-buildpack-phoenix-static) to handle them: ``` $ heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git Buildpack added. Next release on mysterious-meadow-6277 will use: 1. https://github.com/HashNuke/heroku-buildpack-elixir.git 2. https://github.com/gjaldon/heroku-buildpack-phoenix-static.git ``` When using this buildpack, you want to delegate all asset bundling to `npm`. So you must remove the `hook_post_compile` configuration from your `elixir_buildpack.config` and move it to the deploy script of your `assets/package.json`. Something like this: ``` { ... "scripts": { "deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild*" } ... } ``` The Phoenix Static buildpack uses a predefined Node.js version, but to avoid surprises when deploying, it is best to explicitly list the Node.js version we want in production to be the same we are using during development or in your continuous integration servers. This is done by creating a config file named `phoenix_static_buildpack.config` in the root directory of your project with your target version of Node.js: ``` # Node.js version node_version=10.20.1 ``` Please refer to the [configuration section](https://github.com/gjaldon/heroku-buildpack-phoenix-static#configuration) for full details. You can make your own custom build script, but for now we will use the [default one provided](https://github.com/gjaldon/heroku-buildpack-phoenix-static/blob/master/compile). Finally, note that since we are using multiple buildpacks, you might run into an issue where the sequence is out of order (the Elixir buildpack needs to run before the Phoenix Static buildpack). [Heroku's docs](https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app) explain this better, but you will need to make sure the Phoenix Static buildpack comes last. ## [](heroku.html#making-our-project-ready-for-heroku)Making our Project ready for Heroku Every new Phoenix project ships with a config file `config/runtime.exs` (formerly `config/prod.secret.exs`) which loads configuration and secrets from [environment variables](https://devcenter.heroku.com/articles/config-vars). This aligns well with Heroku best practices ([12-factor apps](https://12factor.net/)), so the only work left for us to do is to configure URLs and SSL. First let's tell Phoenix to only use the SSL version of the website. Find the endpoint config in your `config/prod.exs`: ``` config :scaffold, ScaffoldWeb.Endpoint, url: [port: 443, scheme: "https"], ``` ... and add `force_ssl` ``` config :scaffold, ScaffoldWeb.Endpoint, url: [port: 443, scheme: "https"], force_ssl: [rewrite_on: [:x_forwarded_proto]], ``` `force_ssl` need to be set here because it is a *compile* time config. It will not work when set from `runtime.exs`. Then in your `config/runtime.exs` (formerly `config/prod.secret.exs`): ... add `host` ``` config :scaffold, ScaffoldWeb.Endpoint, url: [host: host, port: 443, scheme: "https"] ``` and uncomment the `# ssl: true,` line in your repository configuration. It will look like this: ``` config :hello, Hello.Repo, ssl: true, url: database_url, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") ``` Finally, if you plan on using websockets, then we will need to decrease the timeout for the websocket transport in `lib/hello_web/endpoint.ex`. If you do not plan on using websockets, then leaving it set to false is fine. You can find further explanation of the options available at the [documentation](Phoenix.Endpoint.html#socket/3-websocket-configuration). ``` defmodule HelloWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello socket "/socket", HelloWeb.UserSocket, websocket: [timeout: 45_000] ... end ``` Also set the host in Heroku: ``` $ heroku config:set PHX_HOST="mysterious-meadow-6277.herokuapp.com" ``` This ensures that any idle connections are closed by Phoenix before they reach Heroku's 55-second timeout window. ## [](heroku.html#creating-environment-variables-in-heroku)Creating Environment Variables in Heroku The `DATABASE_URL` config var is automatically created by Heroku when we add the [Heroku Postgres add-on](https://elements.heroku.com/addons/heroku-postgresql). We can create the database via the Heroku toolbelt: ``` $ heroku addons:create heroku-postgresql:mini ``` Now we set the `POOL_SIZE` config var: ``` $ heroku config:set POOL_SIZE=18 ``` This value should be just under the number of available connections, leaving a couple open for migrations and mix tasks. The mini database allows 20 connections, so we set this number to 18. If additional dynos will share the database, reduce the `POOL_SIZE` to give each dyno an equal share. When running a mix task later (after we have pushed the project to Heroku) you will also want to limit its pool size like so: ``` $ heroku run "POOL_SIZE=2 mix hello.task" ``` So that Ecto does not attempt to open more than the available connections. We still have to create the `SECRET_KEY_BASE` config based on a random string. First, use [`mix phx.gen.secret`](Mix.Tasks.Phx.Gen.Secret.html) to get a new secret: ``` $ mix phx.gen.secret xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53 ``` Your random string will be different; don't use this example value. Now set it in Heroku: ``` $ heroku config:set SECRET_KEY_BASE="xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53" Setting config vars and restarting mysterious-meadow-6277... done, v3 SECRET_KEY_BASE: xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53 ``` ## [](heroku.html#deploy-time)Deploy Time! Our project is now ready to be deployed on Heroku. Let's commit all our changes: ``` $ git add elixir_buildpack.config $ git commit -a -m "Use production config from Heroku ENV variables and decrease socket timeout" ``` And deploy: ``` $ git push heroku main Counting objects: 55, done. Delta compression using up to 8 threads. Compressing objects: 100% (49/49), done. Writing objects: 100% (55/55), 48.48 KiB | 0 bytes/s, done. Total 55 (delta 1), reused 0 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Multipack app detected remote: -----> Fetching custom git buildpack... done remote: -----> elixir app detected remote: -----> Checking Erlang and Elixir versions remote: WARNING: elixir_buildpack.config wasn't found in the app remote: Using default config from Elixir buildpack remote: Will use the following versions: remote: * Stack cedar-14 remote: * Erlang 17.5 remote: * Elixir 1.0.4 remote: Will export the following config vars: remote: * Config vars DATABASE_URL remote: * MIX_ENV=prod remote: -----> Stack changed, will rebuild remote: -----> Fetching Erlang 17.5 remote: -----> Installing Erlang 17.5 (changed) remote: remote: -----> Fetching Elixir v1.0.4 remote: -----> Installing Elixir v1.0.4 (changed) remote: -----> Installing Hex remote: 2015-07-07 00:04:00 URL:https://s3.amazonaws.com/s3.hex.pm/installs/1.0.0/hex.ez [262010/262010] -> "/app/.mix/archives/hex.ez" [1] remote: * creating /app/.mix/archives/hex.ez remote: -----> Installing rebar remote: * creating /app/.mix/rebar remote: -----> Fetching app dependencies with mix remote: Running dependency resolution remote: Dependency resolution completed successfully remote: [...] remote: -----> Compiling remote: [...] remote: Generated phoenix_heroku app remote: [...] remote: Consolidated protocols written to _build/prod/consolidated remote: -----> Creating .profile.d with env vars remote: -----> Fetching custom git buildpack... done remote: -----> Phoenix app detected remote: remote: -----> Loading configuration and environment remote: Loading config... remote: [...] remote: Will export the following config vars: remote: * Config vars DATABASE_URL remote: * MIX_ENV=prod remote: remote: -----> Compressing... done, 82.1MB remote: -----> Launching... done, v5 remote: https://mysterious-meadow-6277.herokuapp.com/ deployed to Heroku remote: remote: Verifying deploy... done. To https://git.heroku.com/mysterious-meadow-6277.git * [new branch] master -> master ``` Typing `heroku open` in the terminal should launch a browser with the Phoenix welcome page opened. In the event that you are using Ecto to access a database, you will also need to run migrations after the first deploy: ``` $ heroku run "POOL_SIZE=2 mix ecto.migrate" ``` And that's it! ## [](heroku.html#deploying-to-heroku-using-the-container-stack)Deploying to Heroku using the container stack ### [](heroku.html#create-heroku-application)Create Heroku application Set the stack of your app to `container`, this allows us to use `Dockerfile` to define our app setup. ``` $ heroku create Creating app... done, ⬢ mysterious-meadow-6277 $ heroku stack:set container ``` Add a new `heroku.yml` file to your root folder. In this file you can define addons used by your app, how to build the image and what configs are passed to the image. You can learn more about Heroku's `heroku.yml` options [here](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml). Here is a sample: ``` setup: addons: - plan: heroku-postgresql as: DATABASE build: docker: web: Dockerfile config: MIX_ENV: prod SECRET_KEY_BASE: $SECRET_KEY_BASE DATABASE_URL: $DATABASE_URL ``` ### [](heroku.html#set-up-releases-and-dockerfile)Set up releases and Dockerfile Now we need to define a `Dockerfile` at the root folder of your project that contains your application. We recommend to use releases when doing so, as the release will allow us to build a container with only the parts of Erlang and Elixir we actually use. Follow the [releases docs](releases.html). At the end of the guide, there is a sample Dockerfile file you can use. Once you have the image definition set up, you can push your app to heroku and you can see it starts building the image and deploy it. ## [](heroku.html#useful-heroku-commands)Useful Heroku Commands We can look at the logs of our application by running the following command in our project directory: ``` $ heroku logs # use --tail if you want to tail them ``` We can also start an IEx session attached to our terminal for experimenting in our app's environment: ``` $ heroku run "POOL_SIZE=2 iex -S mix" ``` In fact, we can run anything using the `heroku run` command, like the Ecto migration task from above: ``` $ heroku run "POOL_SIZE=2 mix ecto.migrate" ``` ## [](heroku.html#connecting-to-your-dyno)Connecting to your dyno Heroku gives you the ability to connect to your dyno with an IEx shell which allows running Elixir code such as database queries. - Modify the `web` process in your Procfile to run a named node: ``` web: elixir --sname server -S mix phx.server ``` - Redeploy to Heroku - Connect to the dyno with `heroku ps:exec` (if you have several applications on the same repository you will need to specify the app name or the remote name with `--app APP_NAME` or `--remote REMOTE_NAME`) - Launch an iex session with `iex --sname console --remsh server` You have an iex session into your dyno! ## [](heroku.html#troubleshooting)Troubleshooting ### [](heroku.html#compilation-error)Compilation Error Occasionally, an application will compile locally, but not on Heroku. The compilation error on Heroku will look something like this: ``` remote: == Compilation error on file lib/postgrex/connection.ex == remote: could not compile dependency :postgrex, "mix compile" failed. You can recompile this dependency with "mix deps.compile postgrex", update it with "mix deps.update postgrex" or clean it with "mix deps.clean postgrex" remote: ** (CompileError) lib/postgrex/connection.ex:207: Postgrex.Connection.__struct__/0 is undefined, cannot expand struct Postgrex.Connection remote: (elixir) src/elixir_map.erl:58: :elixir_map.translate_struct/4 remote: (stdlib) lists.erl:1353: :lists.mapfoldl/3 remote: (stdlib) lists.erl:1354: :lists.mapfoldl/3 remote: remote: remote: ! Push rejected, failed to compile elixir app remote: remote: Verifying deploy... remote: remote: ! Push rejected to mysterious-meadow-6277. remote: To https://git.heroku.com/mysterious-meadow-6277.git ``` This has to do with stale dependencies which are not getting recompiled properly. It's possible to force Heroku to recompile all dependencies on each deploy, which should fix this problem. The way to do it is to add a new file called `elixir_buildpack.config` at the root of the application. The file should contain this line: ``` always_rebuild=true ``` Commit this file to the repository and try to push again to Heroku. ### [](heroku.html#connection-timeout-error)Connection Timeout Error If you are constantly getting connection timeouts while running `heroku run` this could mean that your internet provider has blocked port number 5000: ``` heroku run "POOL_SIZE=2 mix myapp.task" Running POOL_SIZE=2 mix myapp.task on mysterious-meadow-6277... ! ETIMEDOUT: connect ETIMEDOUT 50.19.103.36:5000 ``` You can overcome this by adding `detached` option to run command: ``` heroku run:detached "POOL_SIZE=2 mix ecto.migrate" Running POOL_SIZE=2 mix ecto.migrate on mysterious-meadow-6277... done, run.8089 (Free) ``` [← Previous Page Deploying on Fly.io](fly.html) [Next Page → Routing cheatsheet](router.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/deployment/heroku.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/installation.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/introduction/installation.md#L1 "View Source") Installation In order to build a Phoenix application, we will need a few dependencies installed in our Operating System: - the Erlang VM and the Elixir programming language - a database - Phoenix recommends PostgreSQL, but you can pick others or not use a database at all - and other optional packages. Please take a look at this list and make sure to install anything necessary for your system. Having dependencies installed in advance can prevent frustrating problems later on. ## [](installation.html#elixir-1-14-or-later)Elixir 1.14 or later Phoenix is written in Elixir, and our application code will also be written in Elixir. We won't get far in a Phoenix app without it! The Elixir site maintains a great [Installation Page](https://elixir-lang.org/install.html) to help. ## [](installation.html#erlang-24-or-later)Erlang 24 or later Elixir code compiles to Erlang byte code to run on the Erlang virtual machine. Without Erlang, Elixir code has no virtual machine to run on, so we need to install Erlang as well. When we install Elixir using instructions from the Elixir [Installation Page](https://elixir-lang.org/install.html), we will usually get Erlang too. If Erlang was not installed along with Elixir, please see the [Erlang Instructions](https://elixir-lang.org/install.html#installing-erlang) section of the Elixir Installation Page for instructions. ## [](installation.html#phoenix)Phoenix To check that we are on Elixir 1.14 and Erlang 24 or later, run: ``` elixir -v Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] Elixir 1.14.0 ``` Once we have Elixir and Erlang, we are ready to install the Phoenix application generator: ``` $ mix archive.install hex phx_new ``` The `phx.new` generator is now available to generate new applications in the next guide, called [Up and Running](up_and_running.html). The flags mentioned below are command line options to the generator; see all available options by calling [`mix help phx.new`](Mix.Tasks.Phx.New.html). ## [](installation.html#postgresql)PostgreSQL PostgreSQL is a relational database server. Phoenix configures applications to use it by default, but we can switch to MySQL, MSSQL, or SQLite3 by passing the `--database` flag when creating a new application. In order to talk to databases, Phoenix applications use another Elixir package, called [Ecto](https://github.com/elixir-ecto/ecto). If you don't plan to use databases in your application, you can pass the `--no-ecto` flag. However, if you are just getting started with Phoenix, we recommend you to install PostgreSQL and make sure it is running. The PostgreSQL wiki has [installation guides](https://wiki.postgresql.org/wiki/Detailed_installation_guides) for a number of different systems. ## [](installation.html#inotify-tools-for-linux-users)inotify-tools (for Linux users) Phoenix provides a very handy feature called Live Reloading. As you change your views or your assets, it automatically reloads the page in the browser. In order for this functionality to work, you need a filesystem watcher. macOS and Windows users already have a filesystem watcher, but Linux users must install inotify-tools. Please consult the [inotify-tools wiki](https://github.com/rvoicilas/inotify-tools/wiki) for distribution-specific installation instructions. ## [](installation.html#summary)Summary At the end of this section, you must have installed Elixir, Hex, Phoenix, and PostgreSQL. Now that we have everything installed, let's create our first Phoenix application and get [up and running](up_and_running.html). [← Previous Page Overview](overview.html) [Next Page → Up and Running](up_and_running.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/introduction/installation.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/json_and_apis.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/json_and_apis.md#L1 "View Source") JSON and APIs > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Controllers guide](controllers.html). You can also use the Phoenix Framework to build [Web APIs](https://en.wikipedia.org/wiki/Web_API). By default Phoenix supports JSON but you can bring any other rendering format you desire. ## [](json_and_apis.html#the-json-api)The JSON API For this guide let's create a simple JSON API to store our favourite links, that will support all the CRUD (Create, Read, Update, Delete) operations out of the box. For this guide, we will use Phoenix generators to scaffold our API infrastructure: ``` mix phx.gen.json Urls Url urls link:string title:string * creating lib/hello_web/controllers/url_controller.ex * creating lib/hello_web/controllers/url_json.ex * creating lib/hello_web/controllers/changeset_json.ex * creating test/hello_web/controllers/url_controller_test.exs * creating lib/hello_web/controllers/fallback_controller.ex * creating lib/hello/urls/url.ex * creating priv/repo/migrations/20221129120234_create_urls.exs * creating lib/hello/urls.ex * injecting lib/hello/urls.ex * creating test/hello/urls_test.exs * injecting test/hello/urls_test.exs * creating test/support/fixtures/urls_fixtures.ex * injecting test/support/fixtures/urls_fixtures.ex ``` We will break those files into four categories: - Files in `lib/hello_web` responsible for effectively rendering JSON - Files in `lib/hello` responsible for defining our context and logic to persist links to the database - Files in `priv/repo/migrations` responsible for updating our database - Files in `test` to test our controllers and contexts In this guide, we will explore only the first category of files. To learn more about how Phoenix stores and manage data, check out [the Ecto guide](ecto.html) and [the Contexts guide](contexts.html) for more information. We also have a whole section dedicated to testing. At the end, the generator asks us to add the `/url` resource to our `:api` scope in `lib/hello_web/router.ex`: ``` scope "/api", HelloWeb do pipe_through :api resources "/urls", UrlController, except: [:new, :edit] end ``` The API scope uses the `:api` pipeline, which will run specific steps such as ensuring the client can handle JSON responses. Then we need to update our repository by running migrations: ``` mix ecto.migrate ``` ### [](json_and_apis.html#trying-out-the-json-api)Trying out the JSON API Before we go ahead and change those files, let's take a look at how our API behaves from the command line. First, we need to start the server: ``` mix phx.server ``` Next, let's make a smoke test to check our API is working with: ``` curl -i http://localhost:4000/api/urls ``` If everything went as planned we should get a `200` response: ``` HTTP/1.1 200 OK cache-control: max-age=0, private, must-revalidate content-length: 11 content-type: application/json; charset=utf-8 date: Fri, 06 May 2022 21:22:42 GMT server: Cowboy x-request-id: Fuyg-wMl4S-hAfsAAAUk {"data":[]} ``` We didn't get any data because we haven't populated the database with any yet. So let's add some links: ``` curl -iX POST http://localhost:4000/api/urls \ -H 'Content-Type: application/json' \ -d '{"url": {"link":"https://phoenixframework.org", "title":"Phoenix Framework"}}' curl -iX POST http://localhost:4000/api/urls \ -H 'Content-Type: application/json' \ -d '{"url": {"link":"https://elixir-lang.org", "title":"Elixir"}}' ``` Now we can retrieve all links: ``` curl -i http://localhost:4000/api/urls ``` Or we can just retrieve a link by its `id`: ``` curl -i http://localhost:4000/api/urls/1 ``` Next, we can update a link with: ``` curl -iX PUT http://localhost:4000/api/urls/2 \ -H 'Content-Type: application/json' \ -d '{"url": {"title":"Elixir Programming Language"}}' ``` The response should be a `200` with the updated link in the body. Finally, we need to try out the removal of a link: ``` curl -iX DELETE http://localhost:4000/api/urls/2 \ -H 'Content-Type: application/json' ``` A `204` response should be returned to indicate the successful removal of the link. ## [](json_and_apis.html#rendering-json)Rendering JSON To understand how to render JSON, let's start with the `index` action from `UrlController` defined at `lib/hello_web/controllers/url_controller.ex`: ``` def index(conn, _params) do urls = Urls.list_urls() render(conn, :index, urls: urls) end ``` As we can see, this is not any different from how Phoenix renders HTML templates. We call `render/3`, passing the connection, the template we want our views to render (`:index`), and the data we want to make available to our views. Phoenix typically uses one view per rendering format. When rendering HTML, we would use `UrlHTML`. Now that we are rendering JSON, we will find a `UrlJSON` view collocated with the template at `lib/hello_web/controllers/url_json.ex`. Let's open it up: ``` defmodule HelloWeb.UrlJSON do alias Hello.Urls.Url @doc """ Renders a list of urls. """ def index(%{urls: urls}) do %{data: for(url <- urls, do: data(url))} end @doc """ Renders a single url. """ def show(%{url: url}) do %{data: data(url)} end defp data(%Url{} = url) do %{ id: url.id, link: url.link, title: url.title } end end ``` This view is very simple. The `index` function receives all URLs, and converts them into a list of maps. Those maps are placed inside the data key at the root, exactly as we saw when interfacing with our application from `cURL`. In other words, our JSON view converts our complex data into simple Elixir data-structures. Once our view layer returns, Phoenix uses the [`Jason`](../jason/1.4.0/Jason.html) library to encode JSON and send the response to the client. If you explore the remaining the controller, you will learn the `show` action is similar to the `index` one. For `create`, `update`, and `delete` actions, Phoenix uses one other important feature, called "Action fallback". ## [](json_and_apis.html#action-fallback)Action fallback Action fallback allows us to centralize error handling code in plugs, which are called when a controller action fails to return a [`%Plug.Conn{}`](../plug/1.15.3/Plug.Conn.html#t:t/0) struct. These plugs receive both the `conn` which was originally passed to the controller action along with the return value of the action. Let's say we have a `show` action which uses [`with`](../elixir/Kernel.SpecialForms.html#with/1) to fetch a blog post and then authorize the current user to view that blog post. In this example we might expect `fetch_post/1` to return `{:error, :not_found}` if the post is not found and `authorize_user/3` might return `{:error, :unauthorized}` if the user is unauthorized. We could use our `ErrorHTML` and `ErrorJSON` views which are generated by Phoenix for every new application to handle these error paths accordingly: ``` defmodule HelloWeb.MyController do use Phoenix.Controller def show(conn, %{"id" => id}, current_user) do with {:ok, post} <- fetch_post(id), :ok <- authorize_user(current_user, :view, post) do render(conn, :show, post: post) else {:error, :not_found} -> conn |> put_status(:not_found) |> put_view(html: HelloWeb.ErrorHTML, json: HelloWeb.ErrorJSON) |> render(:"404") {:error, :unauthorized} -> conn |> put_status(403) |> put_view(html: HelloWeb.ErrorHTML, json: HelloWeb.ErrorJSON) |> render(:"403") end end end ``` Now imagine you may need to implement similar logic for every controller and action handled by your API. This would result in a lot of repetition. Instead we can define a module plug which knows how to handle these error cases specifically. Since controllers are module plugs, let's define our plug as a controller: ``` defmodule HelloWeb.MyFallbackController do use Phoenix.Controller def call(conn, {:error, :not_found}) do conn |> put_status(:not_found) |> put_view(json: HelloWeb.ErrorJSON) |> render(:"404") end def call(conn, {:error, :unauthorized}) do conn |> put_status(403) |> put_view(json: HelloWeb.ErrorJSON) |> render(:"403") end end ``` Then we can reference our new controller as the `action_fallback` and simply remove the `else` block from our `with`: ``` defmodule HelloWeb.MyController do use Phoenix.Controller action_fallback HelloWeb.MyFallbackController def show(conn, %{"id" => id}, current_user) do with {:ok, post} <- fetch_post(id), :ok <- authorize_user(current_user, :view, post) do render(conn, :show, post: post) end end end ``` Whenever the `with` conditions do not match, `HelloWeb.MyFallbackController` will receive the original `conn` as well as the result of the action and respond accordingly. ## [](json_and_apis.html#fallbackcontroller-and-changesetjson)FallbackController and ChangesetJSON With this knowledge in hand, we can explore the `FallbackController` (`lib/hello_web/controllers/fallback_controller.ex`) generated by [`mix phx.gen.json`](Mix.Tasks.Phx.Gen.Json.html). In particular, it handles one clause (the other is generated as an example): ``` def call(conn, {:error, %Ecto.Changeset{} = changeset}) do conn |> put_status(:unprocessable_entity) |> put_view(json: HelloWeb.ChangesetJSON) |> render(:error, changeset: changeset) end ``` The goal of this clause is to handle the `{:error, changeset}` return types from the `HelloWeb.Urls` context and render them into rendered errors via the `ChangesetJSON` view. Let's open up `lib/hello_web/controllers/changeset_json.ex` to learn more: ``` defmodule HelloWeb.ChangesetJSON do @doc """ Renders changeset errors. """ def error(%{changeset: changeset}) do # When encoded, the changeset returns its errors # as a JSON object. So we just pass it forward. %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)} end end ``` As we can see, it will convert the errors into a data structure, which will be rendered as JSON. The changeset is a data structure responsible for casting and validating data. For our example, it is defined in `Hello.Urls.Url.changeset/1`. Let's open up `lib/hello/urls/url.ex` and see its definition: ``` @doc false def changeset(url, attrs) do url |> cast(attrs, [:link, :title]) |> validate_required([:link, :title]) end ``` As you can see, the changeset requires both link and title to be given. This means we can try posting a url with no link and title and see how our API responds: ``` curl -iX POST http://localhost:4000/api/urls \ -H 'Content-Type: application/json' \ -d '{"url": {}}' {"errors": {"link": ["can't be blank"], "title": ["can't be blank"]}} ``` Feel free to modify the `changeset` function and see how your API behaves. ## [](json_and_apis.html#api-only-applications)API-only applications In case you want to generate a Phoenix application exclusively for APIs, you can pass several options when invoking [`mix phx.new`](Mix.Tasks.Phx.New.html). Let's check which `--no-*` flags we need to use to not generate the scaffolding that isn't necessary on our Phoenix application for the REST API. From your terminal run: ``` mix help phx.new ``` The output should contain the following: ``` • --no-assets - equivalent to --no-esbuild and --no-tailwind • --no-dashboard - do not include Phoenix.LiveDashboard • --no-ecto - do not generate Ecto files • --no-esbuild - do not include esbuild dependencies and assets. We do not recommend setting this option, unless for API only applications, as doing so requires you to manually add and track JavaScript dependencies • --no-gettext - do not generate gettext files • --no-html - do not generate HTML views • --no-live - comment out LiveView socket setup in your Endpoint and assets/js/app.js. Automatically disabled if --no-html is given • --no-mailer - do not generate Swoosh mailer files • --no-tailwind - do not include tailwind dependencies and assets. The generated markup will still include Tailwind CSS classes, those are left-in as reference for the subsequent styling of your layout and components ``` The `--no-html` is the obvious one we want to use when creating any Phoenix application for an API in order to leave out all the unnecessary HTML scaffolding. You may also pass `--no-assets`, if you don't want any of the asset management bit, `--no-gettext` if you don't support internationalization, and so on. Also bear in mind that nothing stops you to have a backend that supports simultaneously the REST API and a Web App (HTML, assets, internationalization and sockets). [← Previous Page Contexts](contexts.html) [Next Page → Mix tasks](mix_tasks.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/json_and_apis.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/mix_phx_gen_auth.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/authentication/mix_phx_gen_auth.md#L1 "View Source") mix phx.gen.auth The [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) command generates a flexible, pre-built authentication system into your Phoenix app. This generator allows you to quickly move past the task of adding authentication to your codebase and stay focused on the real-world problem your application is trying to solve. ## [](mix_phx_gen_auth.html#getting-started)Getting started > Before running this command, consider committing your work as it generates multiple files. Let's start by running the following command from the root of our app (or `apps/my_app_web` in an umbrella app): ``` $ mix phx.gen.auth Accounts User users An authentication system can be created in two different ways: - Using Phoenix.LiveView (default) - Using Phoenix.Controller only Do you want to create a LiveView based authentication system? [Y/n] Y ``` The authentication generators support Phoenix LiveView, for enhanced UX, so we'll answer `Y` here. You may also answer `n` for a controller based authentication system. Either approach will create an `Accounts` context with an `Accounts.User` schema module. The final argument is the plural version of the schema module, which is used for generating database table names and route paths. The [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) generator is similar to [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html) except it does not accept a list of additional fields to add to the schema, and it generates many more context functions. Since this generator installed additional dependencies in `mix.exs`, let's fetch those: ``` $ mix deps.get ``` Now we need to verify the database connection details for the development and test environments in `config/` so the migrator and tests can run properly. Then run the following to create the database: ``` $ mix ecto.setup ``` Let's run the tests to make sure our new authentication system works as expected. ``` $ mix test ``` And finally, let's start our Phoenix server and try it out. ``` $ mix phx.server ``` ## [](mix_phx_gen_auth.html#developer-responsibilities)Developer responsibilities Since Phoenix generates this code into your application instead of building these modules into Phoenix itself, you now have complete freedom to modify the authentication system, so it works best with your use case. The one caveat with using a generated authentication system is it will not be updated after it's been generated. Therefore, as improvements are made to the output of [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html), it becomes your responsibility to determine if these changes need to be ported into your application. Security-related and other important improvements will be explicitly and clearly marked in the `CHANGELOG.md` file and upgrade notes. ## [](mix_phx_gen_auth.html#generated-code)Generated code The following are notes about the generated authentication system. ### [](mix_phx_gen_auth.html#password-hashing)Password hashing The password hashing mechanism defaults to `bcrypt` for Unix systems and `pbkdf2` for Windows systems. Both systems use the [Comeonin interface](../comeonin/index.html). The password hashing mechanism can be overridden with the `--hashing-lib` option. The following values are supported: - `bcrypt` - [bcrypt\_elixir](https://hex.pm/packages/bcrypt_elixir) - `pbkdf2` - [pbkdf2\_elixir](https://hex.pm/packages/pbkdf2_elixir) - `argon2` - [argon2\_elixir](https://hex.pm/packages/argon2_elixir) We recommend developers to consider using `argon2`, which is the most robust of all 3. The downside is that `argon2` is quite CPU and memory intensive, and you will need more powerful instances to run your applications on. For more information about choosing these libraries, see the [Comeonin project](https://github.com/riverrun/comeonin). ### [](mix_phx_gen_auth.html#forbidding-access)Forbidding access The generated code ships with an authentication module with a handful of plugs that fetch the current user, require authentication and so on. For instance, in an app named Demo which had `mix phx.gen.auth Accounts User users` run on it, you will find a module named `DemoWeb.UserAuth` with plugs such as: - `fetch_current_user` - fetches the current user information if available - `require_authenticated_user` - must be invoked after `fetch_current_user` and requires that a current user exists and is authenticated - `redirect_if_user_is_authenticated` - used for the few pages that must not be available to authenticated users ### [](mix_phx_gen_auth.html#confirmation)Confirmation The generated functionality ships with an account confirmation mechanism, where users have to confirm their account, typically by email. However, the generated code does not forbid users from using the application if their accounts have not yet been confirmed. You can add this functionality by customizing the `require_authenticated_user` in the `Auth` module to check for the `confirmed_at` field (and any other property you desire). ### [](mix_phx_gen_auth.html#notifiers)Notifiers The generated code is not integrated with any system to send SMSes or emails for confirming accounts, resetting passwords, etc. Instead, it simply logs a message to the terminal. It is your responsibility to integrate with the proper system after generation. Note that if you generated your Phoenix project with [`mix phx.new`](Mix.Tasks.Phx.New.html), your project is configured to use [Swoosh](https://hexdocs.pm/swoosh/Swoosh.html) mailer by default. To view notifier emails during development with Swoosh, navigate to `/dev/mailbox`. ### [](mix_phx_gen_auth.html#tracking-sessions)Tracking sessions All sessions and tokens are tracked in a separate table. This allows you to track how many sessions are active for each account. You could even expose this information to users if desired. Note that whenever the password changes (either via reset password or directly), all tokens are deleted, and the user has to log in again on all devices. ### [](mix_phx_gen_auth.html#user-enumeration-attacks)User Enumeration attacks A user enumeration attack allows someone to check if an email is registered in the application. The generated authentication code does not attempt to protect from such checks. For instance, when you register an account, if the email is already registered, the code will notify the user the email is already registered. If your application is sensitive to enumeration attacks, you need to implement your own workflows, which tends to be very different from most applications, as you need to carefully balance security and user experience. Furthermore, if you are concerned about enumeration attacks, beware of timing attacks too. For example, registering a new account typically involves additional work (such as writing to the database, sending emails, etc) compared to when an account already exists. Someone could measure the time taken to execute those additional tasks to enumerate emails. This applies to all endpoints (registration, confirmation, password recovery, etc.) that may send email, in-app notifications, etc. ### [](mix_phx_gen_auth.html#case-sensitiveness)Case sensitiveness The email lookup is made to be case-insensitive. Case-insensitive lookups are the default in MySQL and MSSQL. In SQLite3 we use [`COLLATE NOCASE`](https://www.sqlite.org/datatype3.html#collating_sequences) in the column definition to support it. In PostgreSQL, we use the [`citext` extension](https://www.postgresql.org/docs/current/citext.html). Note `citext` is part of PostgreSQL itself and is bundled with it in most operating systems and package managers. [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) takes care of creating the extension and no extra work is necessary in the majority of cases. If by any chance your package manager splits `citext` into a separate package, you will get an error while migrating, and you can most likely solve it by installing the `postgres-contrib` package. ### [](mix_phx_gen_auth.html#concurrent-tests)Concurrent tests The generated tests run concurrently if you are using a database that supports concurrent tests, which is the case of PostgreSQL. ## [](mix_phx_gen_auth.html#more-about-mix-phx-gen-auth)More about [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) Check out [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) for more details, such as using a different password hashing library, customizing the web module namespace, generating binary id type, configuring the default options, and using custom table names. ## [](mix_phx_gen_auth.html#additional-resources)Additional resources The following links have more information regarding the motivation and design of the code this generates. - Berenice Medel's blog post on generating LiveViews for authentication (rather than conventional Controllers &amp; Views) - [Bringing Phoenix Authentication to Life](https://fly.io/phoenix-files/phx-gen-auth/) - José Valim's blog post - [An upcoming authentication solution for Phoenix](https://dashbit.co/blog/a-new-authentication-solution-for-phoenix) - The [original `phx_gen_auth` repo](https://github.com/aaronrenner/phx_gen_auth) (for Phoenix 1.5 applications) - This is a great resource to see discussions around decisions that have been made in earlier versions of the project. - [Original pull request on bare Phoenix app](https://github.com/dashbitco/mix_phx_gen_auth_demo/pull/1) - [Original design spec](https://github.com/dashbitco/mix_phx_gen_auth_demo/blob/auth/README.md) [← Previous Page Asset Management](asset_management.html) [Next Page → API Authentication](api_authentication.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/authentication/mix_phx_gen_auth.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/mix_tasks.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/mix_tasks.md#L1 "View Source") Mix tasks There are currently a number of built-in Phoenix-specific and Ecto-specific [Mix tasks](../mix/Mix.Task.html) available to us within a newly-generated application. We can also create our own application specific tasks. > Note to learn more about `mix`, you can read Elixir's official [Introduction to Mix](../elixir/introduction-to-mix.html). ## [](mix_tasks.html#phoenix-tasks)Phoenix tasks ``` $ mix help --search "phx" mix local.phx # Updates the Phoenix project generator locally mix phx # Prints Phoenix help information mix phx.digest # Digests and compresses static files mix phx.digest.clean # Removes old versions of static assets. mix phx.gen.auth # Generates authentication logic for a resource mix phx.gen.cert # Generates a self-signed certificate for HTTPS testing mix phx.gen.channel # Generates a Phoenix channel mix phx.gen.context # Generates a context with functions around an Ecto schema mix phx.gen.embedded # Generates an embedded Ecto schema file mix phx.gen.html # Generates controller, views, and context for an HTML resource mix phx.gen.json # Generates controller, views, and context for a JSON resource mix phx.gen.live # Generates LiveView, templates, and context for a resource mix phx.gen.notifier # Generates a notifier that delivers emails by default mix phx.gen.presence # Generates a Presence tracker mix phx.gen.schema # Generates an Ecto schema and migration file mix phx.gen.secret # Generates a secret mix phx.gen.socket # Generates a Phoenix socket handler mix phx.new # Creates a new Phoenix application mix phx.new.ecto # Creates a new Ecto project within an umbrella project mix phx.new.web # Creates a new Phoenix web project within an umbrella project mix phx.routes # Prints all routes mix phx.server # Starts applications and their servers ``` We have seen all of these at one point or another in the guides, but having all the information about them in one place seems like a good idea. We will cover all Phoenix Mix tasks, except `phx.new`, `phx.new.ecto`, and `phx.new.web`, which are part of the Phoenix installer. You can learn more about them or any other task by calling `mix help TASK`. ### [](mix_tasks.html#mix-phx-gen-html)[`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html) Phoenix offers the ability to generate all the code to stand up a complete HTML resource — Ecto migration, Ecto context, controller with all the necessary actions, view, and templates. This can be a tremendous time saver. Let's take a look at how to make this happen. The [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html) task takes the following arguments: the module name of the context, the module name of the schema, the resource name, and a list of column\_name:type attributes. The module name we pass in must conform to the Elixir rules of module naming, following proper capitalization. ``` $ mix phx.gen.html Blog Post posts body:string word_count:integer * creating lib/hello_web/controllers/post_controller.ex * creating lib/hello_web/controllers/post_html/edit.html.heex * creating lib/hello_web/controllers/post_html/post_form.html.heex * creating lib/hello_web/controllers/post_html/index.html.heex * creating lib/hello_web/controllers/post_html/new.html.heex * creating lib/hello_web/controllers/post_html/show.html.heex * creating lib/hello_web/controllers/post_html.ex * creating test/hello_web/controllers/post_controller_test.exs * creating lib/hello/blog/post.ex * creating priv/repo/migrations/20211001233016_create_posts.exs * creating lib/hello/blog.ex * injecting lib/hello/blog.ex * creating test/hello/blog_test.exs * injecting test/hello/blog_test.exs * creating test/support/fixtures/blog_fixtures.ex * injecting test/support/fixtures/blog_fixtures.ex ``` When [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html) is done creating files, it helpfully tells us that we need to add a line to our router file as well as run our Ecto migrations. ``` Add the resource to your browser scope in lib/hello_web/router.ex: resources "/posts", PostController Remember to update your repository by running migrations: $ mix ecto.migrate ``` Important: If we don't do this, we will see the following warnings in our logs, and our application will error when compiling. ``` $ mix phx.server Compiling 17 files (.ex) warning: no route path for HelloWeb.Router matches \"/posts\" lib/hello_web/controllers/post_controller.ex:22: HelloWeb.PostController.index/2 ``` If we don't want to create a context or schema for our resource we can use the `--no-context` flag. Note that this still requires a context module name as a parameter. ``` $ mix phx.gen.html Blog Post posts body:string word_count:integer --no-context * creating lib/hello_web/controllers/post_controller.ex * creating lib/hello_web/controllers/post_html/edit.html.heex * creating lib/hello_web/controllers/post_html/post_form.html.heex * creating lib/hello_web/controllers/post_html/index.html.heex * creating lib/hello_web/controllers/post_html/new.html.heex * creating lib/hello_web/controllers/post_html/show.html.heex * creating lib/hello_web/controllers/post_html.ex * creating test/hello_web/controllers/post_controller_test.exs ``` It will tell us we need to add a line to our router file, but since we skipped the context, it won't mention anything about `ecto.migrate`. ``` Add the resource to your browser scope in lib/hello_web/router.ex: resources "/posts", PostController ``` Similarly, if we want a context created without a schema for our resource we can use the `--no-schema` flag. ``` $ mix phx.gen.html Blog Post posts body:string word_count:integer --no-schema * creating lib/hello_web/controllers/post_controller.ex * creating lib/hello_web/controllers/post_html/edit.html.heex * creating lib/hello_web/controllers/post_html/post_form.html.heex * creating lib/hello_web/controllers/post_html/index.html.heex * creating lib/hello_web/controllers/post_html/new.html.heex * creating lib/hello_web/controllers/post_html/show.html.heex * creating lib/hello_web/controllers/post_html.ex * creating test/hello_web/controllers/post_controller_test.exs * creating lib/hello/blog.ex * injecting lib/hello/blog.ex * creating test/hello/blog_test.exs * injecting test/hello/blog_test.exs * creating test/support/fixtures/blog_fixtures.ex * injecting test/support/fixtures/blog_fixtures.ex ``` It will tell us we need to add a line to our router file, but since we skipped the schema, it won't mention anything about `ecto.migrate`. ### [](mix_tasks.html#mix-phx-gen-json)[`mix phx.gen.json`](Mix.Tasks.Phx.Gen.Json.html) Phoenix also offers the ability to generate all the code to stand up a complete JSON resource — Ecto migration, Ecto schema, controller with all the necessary actions and view. This command will not create any template for the app. The [`mix phx.gen.json`](Mix.Tasks.Phx.Gen.Json.html) task takes the following arguments: the module name of the context, the module name of the schema, the resource name, and a list of column\_name:type attributes. The module name we pass in must conform to the Elixir rules of module naming, following proper capitalization. ``` $ mix phx.gen.json Blog Post posts title:string content:string * creating lib/hello_web/controllers/post_controller.ex * creating lib/hello_web/controllers/post_json.ex * creating test/hello_web/controllers/post_controller_test.exs * creating lib/hello_web/controllers/changeset_json.ex * creating lib/hello_web/controllers/fallback_controller.ex * creating lib/hello/blog/post.ex * creating priv/repo/migrations/20170906153323_create_posts.exs * creating lib/hello/blog.ex * injecting lib/hello/blog.ex * creating test/hello/blog/blog_test.exs * injecting test/hello/blog/blog_test.exs * creating test/support/fixtures/blog_fixtures.ex * injecting test/support/fixtures/blog_fixtures.ex ``` When [`mix phx.gen.json`](Mix.Tasks.Phx.Gen.Json.html) is done creating files, it helpfully tells us that we need to add a line to our router file as well as run our Ecto migrations. ``` Add the resource to the "/api" scope in lib/hello_web/router.ex: resources "/posts", PostController, except: [:new, :edit] Remember to update your repository by running migrations: $ mix ecto.migrate ``` Important: If we don't do this, we'll get the following warning in our logs and the application will error when attempting to compile: ``` $ mix phx.server Compiling 19 files (.ex) warning: no route path for HelloWeb.Router matches \"/posts\" lib/hello_web/controllers/post_controller.ex:22: HelloWeb.PostController.index/2 ``` [`mix phx.gen.json`](Mix.Tasks.Phx.Gen.Json.html) also supports `--no-context`, `--no-schema`, and others, as in [`mix phx.gen.html`](Mix.Tasks.Phx.Gen.Html.html). ### [](mix_tasks.html#mix-phx-gen-context)[`mix phx.gen.context`](Mix.Tasks.Phx.Gen.Context.html) If we don't need a complete HTML/JSON resource and only need a context, we can use the [`mix phx.gen.context`](Mix.Tasks.Phx.Gen.Context.html) task. It will generate a context, a schema, a migration and a test case. The [`mix phx.gen.context`](Mix.Tasks.Phx.Gen.Context.html) task takes the following arguments: the module name of the context, the module name of the schema, the resource name, and a list of column\_name:type attributes. ``` $ mix phx.gen.context Accounts User users name:string age:integer * creating lib/hello/accounts/user.ex * creating priv/repo/migrations/20170906161158_create_users.exs * creating lib/hello/accounts.ex * injecting lib/hello/accounts.ex * creating test/hello/accounts/accounts_test.exs * injecting test/hello/accounts/accounts_test.exs * creating test/support/fixtures/accounts_fixtures.ex * injecting test/support/fixtures/accounts_fixtures.ex ``` > Note: If we need to namespace our resource we can simply namespace the first argument of the generator. ``` $ mix phx.gen.context Admin.Accounts User users name:string age:integer * creating lib/hello/admin/accounts/user.ex * creating priv/repo/migrations/20170906161246_create_users.exs * creating lib/hello/admin/accounts.ex * injecting lib/hello/admin/accounts.ex * creating test/hello/admin/accounts_test.exs * injecting test/hello/admin/accounts_test.exs * creating test/support/fixtures/admin/accounts_fixtures.ex * injecting test/support/fixtures/admin/accounts_fixtures.ex ``` ### [](mix_tasks.html#mix-phx-gen-schema)[`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) If we don't need a complete HTML/JSON resource and are not interested in generating or altering a context we can use the [`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) task. It will generate a schema, and a migration. The [`mix phx.gen.schema`](Mix.Tasks.Phx.Gen.Schema.html) task takes the following arguments: the module name of the schema (which may be namespaced), the resource name, and a list of column\_name:type attributes. ``` $ mix phx.gen.schema Accounts.Credential credentials email:string:unique user_id:references:users * creating lib/hello/accounts/credential.ex * creating priv/repo/migrations/20170906162013_create_credentials.exs ``` ### [](mix_tasks.html#mix-phx-gen-auth)[`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) Phoenix also offers the ability to generate all of the code to stand up a complete authentication system — Ecto migration, phoenix context, controllers, templates, etc. This can be a huge time saver, allowing you to quickly add authentication to your system and shift your focus back to the primary problems your application is trying to solve. The [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) task takes the following arguments: the module name of the context, the module name of the schema, and a plural version of the schema name used to generate database tables and route paths. Here is an example version of the command: ``` $ mix phx.gen.auth Accounts User users * creating priv/repo/migrations/20201205184926_create_users_auth_tables.exs * creating lib/hello/accounts/user_notifier.ex * creating lib/hello/accounts/user.ex * creating lib/hello/accounts/user_token.ex * creating lib/hello_web/controllers/user_auth.ex * creating test/hello_web/controllers/user_auth_test.exs * creating lib/hello_web/controllers/user_confirmation_html.ex * creating lib/hello_web/templates/user_confirmation/new.html.heex * creating lib/hello_web/templates/user_confirmation/edit.html.heex * creating lib/hello_web/controllers/user_confirmation_controller.ex * creating test/hello_web/controllers/user_confirmation_controller_test.exs * creating lib/hello_web/templates/user_registration/new.html.heex * creating lib/hello_web/controllers/user_registration_controller.ex * creating test/hello_web/controllers/user_registration_controller_test.exs * creating lib/hello_web/controllers/user_registration_html.ex * creating lib/hello_web/controllers/user_reset_password_html.ex * creating lib/hello_web/controllers/user_reset_password_controller.ex * creating test/hello_web/controllers/user_reset_password_controller_test.exs * creating lib/hello_web/templates/user_reset_password/edit.html.heex * creating lib/hello_web/templates/user_reset_password/new.html.heex * creating lib/hello_web/controllers/user_session_html.ex * creating lib/hello_web/controllers/user_session_controller.ex * creating test/hello_web/controllers/user_session_controller_test.exs * creating lib/hello_web/templates/user_session/new.html.heex * creating lib/hello_web/controllers/user_settings_html.ex * creating lib/hello_web/templates/user_settings/edit.html.heex * creating lib/hello_web/controllers/user_settings_controller.ex * creating test/hello_web/controllers/user_settings_controller_test.exs * creating lib/hello/accounts.ex * injecting lib/hello/accounts.ex * creating test/hello/accounts_test.exs * injecting test/hello/accounts_test.exs * creating test/support/fixtures/accounts_fixtures.ex * injecting test/support/fixtures/accounts_fixtures.ex * injecting test/support/conn_case.ex * injecting config/test.exs * injecting mix.exs * injecting lib/hello_web/router.ex * injecting lib/hello_web/router.ex - imports * injecting lib/hello_web/router.ex - plug * injecting lib/hello_web/templates/layout/root.html.heex ``` When [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) is done creating files, it helpfully tells us that we need to re-fetch our dependencies as well as run our Ecto migrations. ``` Please re-fetch your dependencies with the following command: mix deps.get Remember to update your repository by running migrations: $ mix ecto.migrate Once you are ready, visit "/users/register" to create your account and then access to "/dev/mailbox" to see the account confirmation email. ``` A more complete walk-through of how to get started with this generator is available in the [`mix phx.gen.auth` authentication guide](mix_phx_gen_auth.html). ### [](mix_tasks.html#mix-phx-gen-channel-and-mix-phx-gen-socket)[`mix phx.gen.channel`](Mix.Tasks.Phx.Gen.Channel.html) and [`mix phx.gen.socket`](Mix.Tasks.Phx.Gen.Socket.html) This task will generate a basic Phoenix channel, the socket to power the channel (if you haven't created one yet), as well a test case for it. It takes the module name for the channel as the only argument: ``` $ mix phx.gen.channel Room * creating lib/hello_web/channels/room_channel.ex * creating test/hello_web/channels/room_channel_test.exs ``` If your application does not have a `UserSocket` yet, it will ask if you want to create one: ``` The default socket handler - HelloWeb.UserSocket - was not found in its default location. Do you want to create it? [Y/n] ``` By confirming, a channel will be created, then you need to connect the socket in your endpoint: ``` Add the socket handler to your `lib/hello_web/endpoint.ex`, for example: socket "/socket", HelloWeb.UserSocket, websocket: true, longpoll: false For the front-end integration, you need to import the `user_socket.js` in your `assets/js/app.js` file: import "./user_socket.js" ``` In case a `UserSocket` already exists or you decide to not create one, the `channel` generator will tell you to add it to the Socket manually: ``` Add the channel to your `lib/hello_web/channels/user_socket.ex` handler, for example: channel "rooms:lobby", HelloWeb.RoomChannel ``` You can also create a socket any time by invoking [`mix phx.gen.socket`](Mix.Tasks.Phx.Gen.Socket.html). ### [](mix_tasks.html#mix-phx-gen-presence)[`mix phx.gen.presence`](Mix.Tasks.Phx.Gen.Presence.html) This task will generate a presence tracker. The module name can be passed as an argument, `Presence` is used if no module name is passed. ``` $ mix phx.gen.presence Presence * lib/hello_web/channels/presence.ex Add your new module to your supervision tree, in lib/hello/application.ex: children = [ ... HelloWeb.Presence ] ``` ### [](mix_tasks.html#mix-phx-routes)[`mix phx.routes`](Mix.Tasks.Phx.Routes.html) This task has a single purpose, to show us all the routes defined for a given router. We saw it used extensively in the [routing guide](routing.html). If we don't specify a router for this task, it will default to the router Phoenix generated for us. ``` $ mix phx.routes GET / TaskTester.PageController.index/2 ``` We can also specify an individual router if we have more than one for our application. ``` $ mix phx.routes TaskTesterWeb.Router GET / TaskTesterWeb.PageController.index/2 ``` ### [](mix_tasks.html#mix-phx-server)[`mix phx.server`](Mix.Tasks.Phx.Server.html) This is the task we use to get our application running. It takes no arguments at all. If we pass any in, they will be silently ignored. ``` $ mix phx.server [info] Running TaskTesterWeb.Endpoint with Cowboy on port 4000 (http) ``` It will silently ignore our `DoesNotExist` argument: ``` $ mix phx.server DoesNotExist [info] Running TaskTesterWeb.Endpoint with Cowboy on port 4000 (http) ``` If we would like to start our application and also have an [`IEx`](../iex/IEx.html) session open to it, we can run the Mix task within `iex` like this, `iex -S mix phx.server`. ``` $ iex -S mix phx.server Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] [info] Running TaskTesterWeb.Endpoint with Cowboy on port 4000 (http) Interactive Elixir (1.0.4) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> ``` ### [](mix_tasks.html#mix-phx-digest)[`mix phx.digest`](Mix.Tasks.Phx.Digest.html) This task does two things, it creates a digest for our static assets and then compresses them. "Digest" here refers to an MD5 digest of the contents of an asset which gets added to the filename of that asset. This creates a sort of fingerprint for it. If the digest doesn't change, browsers and CDNs will use a cached version. If it does change, they will re-fetch the new version. Before we run this task let's inspect the contents of two directories in our hello application. First `priv/static/` which should look similar to this: ``` ├── assets │   ├── app.css │   └── app.js ├── favicon.ico └── robots.txt ``` And then `assets/` which should look similar to this: ``` ├── css │   └── app.css ├── js │   └── app.js ├── tailwind.config.js └── vendor └── topbar.js ``` All of these files are our static assets. Now let's run the [`mix phx.digest`](Mix.Tasks.Phx.Digest.html) task. ``` $ mix phx.digest Check your digested files at 'priv/static'. ``` We can now do as the task suggests and inspect the contents of `priv/static/` directory. We'll see that all files from `assets/` have been copied over to `priv/static/` and also each file now has a couple of versions. Those versions are: - the original file - a compressed file with gzip - a file containing the original file name and its digest - a compressed file containing the file name and its digest We can optionally determine which files should be gzipped by using the `:gzippable_exts` option in the config file: ``` config :phoenix, :gzippable_exts, ~w(.js .css) ``` > Note: We can specify a different output folder where [`mix phx.digest`](Mix.Tasks.Phx.Digest.html) will put processed files. The first argument is the path where the static files are located. ``` $ mix phx.digest priv/static/ -o www/public/ Check your digested files at 'www/public/' ``` > Note: You can use [`mix phx.digest.clean`](Mix.Tasks.Phx.Digest.Clean.html) to prune stale versions of the assets. If you want to remove all produced files, run `mix phx.digest.clean --all`. ## [](mix_tasks.html#ecto-tasks)Ecto tasks Newly generated Phoenix applications now include Ecto and Postgrex as dependencies by default (which is to say, unless we use [`mix phx.new`](Mix.Tasks.Phx.New.html) with the `--no-ecto` flag). With those dependencies come Mix tasks to take care of common Ecto operations. Let's see which tasks we get out of the box. ``` $ mix help --search "ecto" mix ecto # Prints Ecto help information mix ecto.create # Creates the repository storage mix ecto.drop # Drops the repository storage mix ecto.dump # Dumps the repository database structure mix ecto.gen.migration # Generates a new migration for the repo mix ecto.gen.repo # Generates a new repository mix ecto.load # Loads previously dumped database structure mix ecto.migrate # Runs the repository migrations mix ecto.migrations # Displays the repository migration status mix ecto.reset # Alias defined in mix.exs mix ecto.rollback # Rolls back the repository migrations mix ecto.setup # Alias defined in mix.exs ``` Note: We can run any of the tasks above with the `--no-start` flag to execute the task without starting the application. ### [](mix_tasks.html#mix-ecto-create)[`mix ecto.create`](../ecto/3.10.1/Mix.Tasks.Ecto.Create.html) This task will create the database specified in our repo. By default it will look for the repo named after our application (the one generated with our app unless we opted out of Ecto), but we can pass in another repo if we want. Here's what it looks like in action. ``` $ mix ecto.create The database for Hello.Repo has been created. ``` There are a few things that can go wrong with `ecto.create`. If our Postgres database doesn't have a "postgres" role (user), we'll get an error like this one. ``` $ mix ecto.create ** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL: role "postgres" does not exist ``` We can fix this by creating the "postgres" role in the `psql` console with the permissions needed to log in and create a database. ``` =# CREATE ROLE postgres LOGIN CREATEDB; CREATE ROLE ``` If the "postgres" role does not have permission to log in to the application, we'll get this error. ``` $ mix ecto.create ** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL: role "postgres" is not permitted to log in ``` To fix this, we need to change the permissions on our "postgres" user to allow login. ``` =# ALTER ROLE postgres LOGIN; ALTER ROLE ``` If the "postgres" role does not have permission to create a database, we'll get this error. ``` $ mix ecto.create ** (Mix) The database for Hello.Repo couldn't be created, reason given: ERROR: permission denied to create database ``` To fix this, we need to change the permissions on our "postgres" user in the `psql` console to allow database creation. ``` =# ALTER ROLE postgres CREATEDB; ALTER ROLE ``` If the "postgres" role is using a password different from the default "postgres", we'll get this error. ``` $ mix ecto.create ** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL: password authentication failed for user "postgres" ``` To fix this, we can change the password in the environment specific configuration file. For the development environment the password used can be found at the bottom of the `config/dev.exs` file. Finally, if we happen to have another repo called `OurCustom.Repo` that we want to create the database for, we can run this. ``` $ mix ecto.create -r OurCustom.Repo The database for OurCustom.Repo has been created. ``` ### [](mix_tasks.html#mix-ecto-drop)[`mix ecto.drop`](../ecto/3.10.1/Mix.Tasks.Ecto.Drop.html) This task will drop the database specified in our repo. By default it will look for the repo named after our application (the one generated with our app unless we opted out of Ecto). It will not prompt us to check if we're sure we want to drop the database, so do exercise caution. ``` $ mix ecto.drop The database for Hello.Repo has been dropped. ``` If we happen to have another repo that we want to drop the database for, we can specify it with the `-r` flag. ``` $ mix ecto.drop -r OurCustom.Repo The database for OurCustom.Repo has been dropped. ``` ### [](mix_tasks.html#mix-ecto-gen-repo)[`mix ecto.gen.repo`](../ecto/3.10.1/Mix.Tasks.Ecto.Gen.Repo.html) Many applications require more than one data store. For each data store, we'll need a new repo, and we can generate them automatically with `ecto.gen.repo`. If we name our repo `OurCustom.Repo`, this task will create it here `lib/our_custom/repo.ex`. ``` $ mix ecto.gen.repo -r OurCustom.Repo * creating lib/our_custom * creating lib/our_custom/repo.ex * updating config/config.exs Don't forget to add your new repo to your supervision tree (typically in lib/hello/application.ex): {OurCustom.Repo, []} ``` Notice that this task has updated `config/config.exs`. If we take a look, we'll see this extra configuration block for our new repo. ``` . . . config :hello, OurCustom.Repo, username: "user", password: "pass", hostname: "localhost", database: "hello_repo", . . . ``` Of course, we'll need to change the login credentials to match what our database expects. We'll also need to change the config for other environments. We certainly should follow the instructions and add our new repo to our supervision tree. In our `Hello` application, we would open up `lib/hello/application.ex`, and add our repo as a worker to the `children` list. ``` . . . children = [ Hello.Repo, # Our custom repo OurCustom.Repo, # Start the endpoint when the application starts HelloWeb.Endpoint, ] . . . ``` ### [](mix_tasks.html#mix-ecto-gen-migration)[`mix ecto.gen.migration`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Gen.Migration.html) Migrations are a programmatic, repeatable way to affect changes to a database schema. Migrations are also just modules, and we can create them with the [`ecto.gen.migration`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Gen.Migration.html) task. Let's walk through the steps to create a migration for a new comments table. We simply need to invoke the task with a `snake_case` version of the module name that we want. Preferably, the name will describe what we want the migration to do. ``` $ mix ecto.gen.migration add_comments_table * creating priv/repo/migrations * creating priv/repo/migrations/20150318001628_add_comments_table.exs ``` Notice that the migration's filename begins with a string representation of the date and time the file was created. Let's take a look at the file `ecto.gen.migration` has generated for us at `priv/repo/migrations/20150318001628_add_comments_table.exs`. ``` defmodule Hello.Repo.Migrations.AddCommentsTable do use Ecto.Migration def change do end end ``` Notice that there is a single function `change/0` which will handle both forward migrations and rollbacks. We'll define the schema changes that we want using Ecto's handy DSL, and Ecto will figure out what to do depending on whether we are rolling forward or rolling back. Very nice indeed. What we want to do is create a `comments` table with a `body` column, a `word_count` column, and timestamp columns for `inserted_at` and `updated_at`. ``` . . . def change do create table(:comments) do add :body, :string add :word_count, :integer timestamps() end end . . . ``` Again, we can run this task with the `-r` flag and another repo if we need to. ``` $ mix ecto.gen.migration -r OurCustom.Repo add_users * creating priv/repo/migrations * creating priv/repo/migrations/20150318172927_add_users.exs ``` For more information on how to modify your database schema please refer to the [Ecto's migration DSL docs](https://hexdocs.pm/ecto_sql/Ecto.Migration.html). For example, to alter an existing schema see the documentation on Ecto’s [`alter/2`](../ecto_sql/3.10.1/Ecto.Migration.html#alter/2) function. That's it! We're ready to run our migration. ### [](mix_tasks.html#mix-ecto-migrate)[`mix ecto.migrate`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Migrate.html) Once we have our migration module ready, we can simply run [`mix ecto.migrate`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Migrate.html) to have our changes applied to the database. ``` $ mix ecto.migrate [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward [info] create table comments [info] == Migrated in 0.1s ``` When we first run `ecto.migrate`, it will create a table for us called `schema_migrations`. This will keep track of all the migrations which we run by storing the timestamp portion of the migration's filename. Here's what the `schema_migrations` table looks like. ``` hello_dev=# select * from schema_migrations; version | inserted_at ---------------+--------------------- 20150317170448 | 2015-03-17 21:07:26 20150318001628 | 2015-03-18 01:45:00 (2 rows) ``` When we roll back a migration, [`ecto.rollback`](mix_tasks.html#mix-ecto-rollback) will remove the record representing this migration from `schema_migrations`. By default, `ecto.migrate` will execute all pending migrations. We can exercise more control over which migrations we run by specifying some options when we run the task. We can specify the number of pending migrations we would like to run with the `-n` or `--step` options. ``` $ mix ecto.migrate -n 2 [info] == Running Hello.Repo.Migrations.CreatePost.change/0 forward [info] create table posts [info] == Migrated in 0.0s [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward [info] create table comments [info] == Migrated in 0.0s ``` The `--step` option will behave the same way. ``` mix ecto.migrate --step 2 ``` The `--to` option will run all migrations up to and including given version. ``` mix ecto.migrate --to 20150317170448 ``` ### [](mix_tasks.html#mix-ecto-rollback)[`mix ecto.rollback`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Rollback.html) The [`ecto.rollback`](../ecto_sql/3.10.1/Mix.Tasks.Ecto.Rollback.html) task will reverse the last migration we have run, undoing the schema changes. [`ecto.migrate`](mix_tasks.html#mix-ecto-migrate) and `ecto.rollback` are mirror images of each other. ``` $ mix ecto.rollback [info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 backward [info] drop table comments [info] == Migrated in 0.0s ``` `ecto.rollback` will handle the same options as `ecto.migrate`, so `-n`, `--step`, `-v`, and `--to` will behave as they do for `ecto.migrate`. ## [](mix_tasks.html#creating-our-own-mix-task)Creating our own Mix task As we've seen throughout this guide, both Mix itself and the dependencies we bring in to our application provide a number of really useful tasks for free. Since neither of these could possibly anticipate all our individual application's needs, Mix allows us to create our own custom tasks. That's exactly what we are going to do now. The first thing we need to do is create a `mix/tasks/` directory inside of `lib/`. This is where any of our application specific Mix tasks will go. ``` $ mkdir -p lib/mix/tasks/ ``` Inside that directory, let's create a new file, `hello.greeting.ex`, that looks like this. ``` defmodule Mix.Tasks.Hello.Greeting do use Mix.Task @shortdoc "Sends a greeting to us from Hello Phoenix" @moduledoc """ This is where we would put any long form documentation and doctests. """ @impl Mix.Task def run(_args) do Mix.shell().info("Greetings from the Hello Phoenix Application!") end # We can define other functions as needed here. end ``` Let's take a quick look at the moving parts involved in a working Mix task. The first thing we need to do is name our module. All tasks must be defined in the `Mix.Tasks` namespace. We'd like to invoke this as `mix hello.greeting`, so we complete the module name with `Hello.Greeting`. The `use Mix.Task` line brings in functionality from Mix that makes this module [behave as a Mix task](../mix/Mix.Task.html). The `@shortdoc` module attribute holds a string which will describe our task when users invoke [`mix help`](../mix/Mix.Tasks.Help.html). `@moduledoc` serves the same function that it does in any module. It's where we can put long-form documentation and doctests, if we have any. The [`run/1`](../mix/Mix.Task.html#c:run/1) function is the critical heart of any Mix task. It's the function that does all the work when users invoke our task. In ours, all we do is send a greeting from our app, but we can implement our `run/1` function to do whatever we need it to. Note that [`Mix.shell().info/1`](../mix/Mix.html#shell/0) is the preferred way to print text back out to the user. Of course, our task is just a module, so we can define other private functions as needed to support our `run/1` function. Now that we have our task module defined, our next step is to compile the application. ``` $ mix compile Compiled lib/tasks/hello.greeting.ex Generated hello.app ``` Now our new task should be visible to [`mix help`](../mix/Mix.Tasks.Help.html). ``` $ mix help --search hello mix hello.greeting # Sends a greeting to us from Hello Phoenix ``` Notice that [`mix help`](../mix/Mix.Tasks.Help.html) displays the text we put into the `@shortdoc` along with the name of our task. So far, so good, but does it work? ``` $ mix hello.greeting Greetings from the Hello Phoenix Application! ``` Indeed it does. If you want to make your new Mix task to use your application's infrastructure, you need to make sure the application is started and configure when Mix task is being executed. This is particularly useful if you need to access your database from within the Mix task. Thankfully, Mix makes it really easy for us via the `@requirements` module attribute: ``` @requirements ["app.start"] @impl Mix.Task def run(_args) do Mix.shell().info("Now I have access to Repo and other goodies!") Mix.shell().info("Greetings from the Hello Phoenix Application!") end ``` [← Previous Page JSON and APIs](json_and_apis.html) [Next Page → Telemetry](telemetry.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/mix_tasks.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/overview.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/introduction/overview.md#L1 "View Source") Overview Phoenix is a web development framework written in Elixir which implements the server-side Model View Controller (MVC) pattern. Many of its components and concepts will seem familiar to those of us with experience in other web frameworks like Ruby on Rails or Python's Django. Phoenix provides the best of both worlds - high developer productivity *and* high application performance. It also has some interesting new twists like channels for implementing realtime features and pre-compiled templates for blazing speed. If you are already familiar with Elixir, great! If not, there are a number of places to learn. The [Elixir guides](../elixir/introduction.html) and the [Elixir learning resources page](https://elixir-lang.org/learning.html) are two great places to start. The guides that you are currently looking at provide an overview of all parts that make Phoenix. Here is a rundown of what they provide: - Introduction - the guides you are currently reading. They will cover how to get your first application up and running - Guides - in-depth guides covering the main components in Phoenix and Phoenix applications - Authentication - in-depth guide covering how to use [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) - Real-time components - in-depth guides covering Phoenix's built-in real-time components - Testing - in-depth guides about testing - Deployment - in-depth guides about deployment - How-to's - a collection of articles on how to achieve certain things with Phoenix If you would prefer to read these guides as an EPUB, [click here!](Phoenix.epub) Note, these guides are not a step-by-step introduction to Phoenix. If you want a more structured approach to learning the framework, we have a large community and many books, courses, and screencasts available. See [our community page](community.html) for a complete list. [Let's get Phoenix installed](installation.html). [← Previous Page Changelog for v1.7](changelog.html) [Next Page → Installation](installation.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/introduction/overview.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/packages_glossary.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/introduction/packages_glossary.md#L1 "View Source") Packages Glossary By default, Phoenix applications depend on several packages with different purposes. This page is a quick reference of the different packages you may work with as a Phoenix developer. The main packages are: - [Ecto](../ecto.html) - a language integrated query and database wrapper - [Phoenix](../phoenix.html) - the Phoenix web framework (these docs) - [Phoenix LiveView](../phoenix_live_view.html) - build rich, real-time user experiences with server-rendered HTML. The LiveView project also defines [`Phoenix.Component`](../phoenix_live_view/Phoenix.Component.html) and [the HEEx template engine](../phoenix_live_view/Phoenix.Component.html#sigil_H/2), used for rendering HTML content in both regular and real-time applications - [Plug](../plug.html) - specification and conveniences for building composable modules web applications. This is the package responsible for the connection abstraction and the regular request- response life-cycle You will also work with the following: - [ExUnit](../ex_unit.html) - Elixir's built-in test framework - [Gettext](../gettext.html) - internationalization and localization through [`gettext`](https://www.gnu.org/software/gettext/) - [Swoosh](../swoosh.html) - a library for composing, delivering and testing emails, also used by [`mix phx.gen.auth`](Mix.Tasks.Phx.Gen.Auth.html) When peeking under the covers, you will find those libraries play an important role in Phoenix applications: - [Phoenix HTML](../phoenix_html.html) - building blocks for working with HTML and forms safely - [Phoenix Ecto](https://hex.pm/packages/phoenix_ecto) - plugs and protocol implementations for using phoenix with ecto - [Phoenix PubSub](../phoenix_pubsub.html) - a distributed pub/sub system with presence support When it comes to instrumentation and monitoring, check out: - [Phoenix LiveDashboard](../phoenix_live_dashboard.html) - real-time performance monitoring and debugging tools for Phoenix developers - [Telemetry Metrics](../telemetry_metrics.html) - common interface for defining metrics based on Telemetry events [← Previous Page Community](community.html) [Next Page → Directory structure](directory_structure.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/introduction/packages_glossary.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/plug.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/plug.md#L1 "View Source") Plug > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html). Plug lives at the heart of Phoenix's HTTP layer, and Phoenix puts Plug front and center. We interact with plugs at every step of the request life-cycle, and the core Phoenix components like endpoints, routers, and controllers are all just plugs internally. Let's jump in and find out just what makes Plug so special. [Plug](https://github.com/elixir-lang/plug) is a specification for composable modules in between web applications. It is also an abstraction layer for connection adapters of different web servers. The basic idea of Plug is to unify the concept of a "connection" that we operate on. This differs from other HTTP middleware layers such as Rack, where the request and response are separated in the middleware stack. At the simplest level, the Plug specification comes in two flavors: *function plugs* and *module plugs*. ## [](plug.html#function-plugs)Function plugs In order to act as a plug, a function needs to: 1. accept a connection struct (`%Plug.Conn{}`) as its first argument, and connection options as its second one; 2. return a connection struct. Any function that meets these two criteria will do. Here's an example. ``` def introspect(conn, _opts) do IO.puts """ Verb: #{inspect(conn.method)} Host: #{inspect(conn.host)} Headers: #{inspect(conn.req_headers)} """ conn end ``` This function does the following: 1. It receives a connection and options (that we do not use) 2. It prints some connection information to the terminal 3. It returns the connection Pretty simple, right? Let's see this function in action by adding it to our endpoint in `lib/hello_web/endpoint.ex`. We can plug it anywhere, so let's do it by inserting `plug :introspect` right before we delegate the request to the router: ``` defmodule HelloWeb.Endpoint do ... plug :introspect plug HelloWeb.Router def introspect(conn, _opts) do IO.puts """ Verb: #{inspect(conn.method)} Host: #{inspect(conn.host)} Headers: #{inspect(conn.req_headers)} """ conn end end ``` Function plugs are plugged by passing the function name as an atom. To try the plug out, go back to your browser and fetch [http://localhost:4000](http://localhost:4000). You should see something like this printed in your shell terminal: ``` Verb: "GET" Host: "localhost" Headers: [...] ``` Our plug simply prints information from the connection. Although our initial plug is very simple, you can do virtually anything you want inside of it. To learn about all fields available in the connection and all of the functionality associated to it, see the [documentation for `Plug.Conn`](https://hexdocs.pm/plug/Plug.Conn.html). Now let's look at the other plug variant, the module plugs. ## [](plug.html#module-plugs)Module plugs Module plugs are another type of plug that let us define a connection transformation in a module. The module only needs to implement two functions: - [`init/1`](../plug/1.15.3/Plug.html#c:init/1) which initializes any arguments or options to be passed to [`call/2`](../plug/1.15.3/Plug.html#c:call/2) - [`call/2`](../plug/1.15.3/Plug.html#c:call/2) which carries out the connection transformation. [`call/2`](../plug/1.15.3/Plug.html#c:call/2) is just a function plug that we saw earlier To see this in action, let's write a module plug that puts the `:locale` key and value into the connection for downstream use in other plugs, controller actions, and our views. Put the contents below in a file named `lib/hello_web/plugs/locale.ex`: ``` defmodule HelloWeb.Plugs.Locale do import Plug.Conn @locales ["en", "fr", "de"] def init(default), do: default def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default) when loc in @locales do assign(conn, :locale, loc) end def call(conn, default) do assign(conn, :locale, default) end end ``` To give it a try, let's add this module plug to our router, by appending `plug HelloWeb.Plugs.Locale, "en"` to our `:browser` pipeline in `lib/hello_web/router.ex`: ``` defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug HelloWeb.Plugs.Locale, "en" end ... ``` In the [`init/1`](../plug/1.15.3/Plug.html#c:init/1) callback, we pass a default locale to use if none is present in the params. We also use pattern matching to define multiple [`call/2`](../plug/1.15.3/Plug.html#c:call/2) function heads to validate the locale in the params, and fall back to `"en"` if there is no match. The [`assign/3`](../plug/1.15.3/Plug.Conn.html#assign/3) is a part of the [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) module and it's how we store values in the `conn` data structure. To see the assign in action, go to the template in `lib/hello_web/controllers/page_html/home.html.heex` and add the following code after the closing of the `</h1>` tag: ``` <p>Locale: {@locale}</p> ``` Go to [http://localhost:4000/](http://localhost:4000/) and you should see the locale exhibited. Visit [http://localhost:4000/?locale=fr](http://localhost:4000/?locale=fr) and you should see the assign changed to `"fr"`. Someone can use this information alongside [Gettext](../gettext/Gettext.html) to provide a fully internationalized web application. That's all there is to Plug. Phoenix embraces the plug design of composable transformations all the way up and down the stack. Let's see some examples! ## [](plug.html#where-to-plug)Where to plug The endpoint, router, and controllers in Phoenix accept plugs. ### [](plug.html#endpoint-plugs)Endpoint plugs Endpoints organize all the plugs common to every request, and apply them before dispatching into the router with its custom pipelines. We added a plug to the endpoint like this: ``` defmodule HelloWeb.Endpoint do ... plug :introspect plug HelloWeb.Router ``` The default endpoint plugs do quite a lot of work. Here they are in order: - [`Plug.Static`](../plug/1.15.3/Plug.Static.html) - serves static assets. Since this plug comes before the logger, requests for static assets are not logged. - `Phoenix.LiveDashboard.RequestLogger` - sets up the *Request Logger* for Phoenix LiveDashboard, this will allow you to have the option to either pass a query parameter to stream requests logs or to enable/disable a cookie that streams requests logs from your dashboard. - [`Plug.RequestId`](../plug/1.15.3/Plug.RequestId.html) - generates a unique request ID for each request. - [`Plug.Telemetry`](../plug/1.15.3/Plug.Telemetry.html) - adds instrumentation points so Phoenix can log the request path, status code and request time by default. - [`Plug.Parsers`](../plug/1.15.3/Plug.Parsers.html) - parses the request body when a known parser is available. By default, this plug can handle URL-encoded, multipart and JSON content (with [`Jason`](../jason/1.4.0/Jason.html)). The request body is left untouched if the request content-type cannot be parsed. - [`Plug.MethodOverride`](../plug/1.15.3/Plug.MethodOverride.html) - converts the request method to PUT, PATCH or DELETE for POST requests with a valid `_method` parameter. - [`Plug.Head`](../plug/1.15.3/Plug.Head.html) - converts HEAD requests to GET requests. - [`Plug.Session`](../plug/1.15.3/Plug.Session.html) - a plug that sets up session management. Note that `fetch_session/2` must still be explicitly called before using the session, as this plug just sets up how the session is fetched. In the middle of the endpoint, there is also a conditional block: ``` if code_reloading? do socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket plug Phoenix.LiveReloader plug Phoenix.CodeReloader plug Phoenix.Ecto.CheckRepoStatus, otp_app: :hello end ``` This block is only executed in development. It enables: - live reloading - if you change a CSS file, they are updated in-browser without refreshing the page; - [code reloading](Phoenix.CodeReloader.html) - so we can see changes to our application without restarting the server; - check repo status - which makes sure our database is up to date, raising a readable and actionable error otherwise. ### [](plug.html#router-plugs)Router plugs In the router, we can declare plugs inside pipelines: ``` defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.LayoutView, :root} plug :protect_from_forgery plug :put_secure_browser_headers plug HelloWeb.Plugs.Locale, "en" end scope "/", HelloWeb do pipe_through :browser get "/", PageController, :index end ``` Routes are defined inside scopes and scopes may pipe through multiple pipelines. Once a route matches, Phoenix invokes all plugs defined in all pipelines associated to that route. For example, accessing "/" will pipe through the `:browser` pipeline, consequently invoking all of its plugs. As we will see in the [routing guide](routing.html), the pipelines themselves are plugs. There, we will also discuss all plugs in the `:browser` pipeline. ### [](plug.html#controller-plugs)Controller plugs Finally, controllers are plugs too, so we can do: ``` defmodule HelloWeb.PageController do use HelloWeb, :controller plug HelloWeb.Plugs.Locale, "en" ``` In particular, controller plugs provide a feature that allows us to execute plugs only within certain actions. For example, you can do: ``` defmodule HelloWeb.PageController do use HelloWeb, :controller plug HelloWeb.Plugs.Locale, "en" when action in [:index] ``` And the plug will only be executed for the `index` action. ## [](plug.html#plugs-as-composition)Plugs as composition By abiding by the plug contract, we turn an application request into a series of explicit transformations. It doesn't stop there. To really see how effective Plug's design is, let's imagine a scenario where we need to check a series of conditions and then either redirect or halt if a condition fails. Without plug, we would end up with something like this: ``` defmodule HelloWeb.MessageController do use HelloWeb, :controller def show(conn, params) do case Authenticator.find_user(conn) do {:ok, user} -> case find_message(params["id"]) do nil -> conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/") message -> if Authorizer.can_access?(user, message) do render(conn, :show, page: message) else conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/") end end :error -> conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/") end end end ``` Notice how just a few steps of authentication and authorization require complicated nesting and duplication? Let's improve this with a couple of plugs. ``` defmodule HelloWeb.MessageController do use HelloWeb, :controller plug :authenticate plug :fetch_message plug :authorize_message def show(conn, params) do render(conn, :show, page: conn.assigns[:message]) end defp authenticate(conn, _) do case Authenticator.find_user(conn) do {:ok, user} -> assign(conn, :user, user) :error -> conn |> put_flash(:info, "You must be logged in") |> redirect(to: ~p"/") |> halt() end end defp fetch_message(conn, _) do case find_message(conn.params["id"]) do nil -> conn |> put_flash(:info, "That message wasn't found") |> redirect(to: ~p"/") |> halt() message -> assign(conn, :message, message) end end defp authorize_message(conn, _) do if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do conn else conn |> put_flash(:info, "You can't access that page") |> redirect(to: ~p"/") |> halt() end end end ``` To make this all work, we converted the nested blocks of code and used `halt(conn)` whenever we reached a failure path. The `halt(conn)` functionality is essential here: it tells Plug that the next plug should not be invoked. At the end of the day, by replacing the nested blocks of code with a flattened series of plug transformations, we are able to achieve the same functionality in a much more composable, clear, and reusable way. To learn more about plugs, see the documentation for the [Plug project](../plug/1.15.3/Plug.html), which provides many built-in plugs and functionalities. [← Previous Page Request life-cycle](request_lifecycle.html) [Next Page → Routing](routing.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/plug.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/presence.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/real_time/presence.md#L1 "View Source") Presence > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Channels guide](channels.html). Phoenix Presence is a feature which allows you to register process information on a topic and replicate it transparently across a cluster. It's a combination of both a server-side and client-side library, which makes it simple to implement. A simple use-case would be showing which users are currently online in an application. Phoenix Presence is special for a number of reasons. It has no single point of failure, no single source of truth, relies entirely on the standard library with no operational dependencies and self-heals. ## [](presence.html#setting-up)Setting up We are going to use Presence to track which users are connected on the server and send updates to the client as users join and leave. We will deliver those updates via Phoenix Channels. Therefore, let's create a `RoomChannel`, as we did in the channels guides: ``` $ mix phx.gen.channel Room ``` Follow the steps after the generator and you are ready to start tracking presence. ## [](presence.html#the-presence-generator)The Presence generator To get started with Presence, we'll first need to generate a presence module. We can do this with the [`mix phx.gen.presence`](Mix.Tasks.Phx.Gen.Presence.html) task: ``` $ mix phx.gen.presence * creating lib/hello_web/channels/presence.ex Add your new module to your supervision tree, in lib/hello/application.ex: children = [ ... HelloWeb.Presence, ] You're all set! See the Phoenix.Presence docs for more details: https://hexdocs.pm/phoenix/Phoenix.Presence.html ``` If we open up the `lib/hello_web/channels/presence.ex` file, we will see the following line: ``` use Phoenix.Presence, otp_app: :hello, pubsub_server: Hello.PubSub ``` This sets up the module for presence, defining the functions we require for tracking presences. As mentioned in the generator task, we should add this module to our supervision tree in `application.ex`: ``` children = [ ... HelloWeb.Presence, ] ``` ## [](presence.html#usage-with-channels-and-javascript)Usage With Channels and JavaScript Next, we will create the channel that we'll communicate presence over. After a user joins, we can push the list of presences down the channel and then track the connection. We can also provide a map of additional information to track. ``` defmodule HelloWeb.RoomChannel do use Phoenix.Channel alias HelloWeb.Presence def join("room:lobby", %{"name" => name}, socket) do send(self(), :after_join) {:ok, assign(socket, :name, name)} end def handle_info(:after_join, socket) do {:ok, _} = Presence.track(socket, socket.assigns.name, %{ online_at: inspect(System.system_time(:second)) }) push(socket, "presence_state", Presence.list(socket)) {:noreply, socket} end end ``` Finally, we can use the client-side Presence library included in `phoenix.js` to manage the state and presence diffs that come down the socket. It listens for the `"presence_state"` and `"presence_diff"` events and provides a simple callback for you to handle the events as they happen, with the `onSync` callback. The `onSync` callback allows you to easily react to presence state changes, which most often results in re-rendering an updated list of active users. You can use the `list` method to format and return each individual presence based on the needs of your application. To iterate users, we use the `presences.list()` function which accepts a callback. The callback will be called for each presence item with 2 arguments, the presence id and a list of metas (one for each presence for that presence id). We use this to display the users and the number of devices they are online with. We can see presence working by adding the following to `assets/js/app.js`: ``` import {Socket, Presence} from "phoenix" let socket = new Socket("/socket", {params: {token: window.userToken}}) let channel = socket.channel("room:lobby", {name: window.location.search.split("=")[1]}) let presence = new Presence(channel) function renderOnlineUsers(presence) { let response = "" presence.list((id, {metas: [first, ...rest]}) => { let count = rest.length + 1 response += `<br>${id} (count: ${count})</br>` }) document.querySelector("main").innerHTML = response } socket.connect() presence.onSync(() => renderOnlineUsers(presence)) channel.join() ``` We can ensure this is working by opening 3 browser tabs. If we navigate to [http://localhost:4000/?name=Alice](http://localhost:4000/?name=Alice) on two browser tabs and [http://localhost:4000/?name=Bob](http://localhost:4000/?name=Bob) then we should see: ``` Alice (count: 2) Bob (count: 1) ``` If we close one of the Alice tabs, then the count should decrease to 1. If we close another tab, the user should disappear from the list entirely. ### [](presence.html#making-it-safe)Making it safe In our initial implementation, we are passing the name of the user as part of the URL. However, in many systems, you want to allow only logged in users to access the presence functionality. To do so, you should set up token authentication, [as detailed in the token authentication section of the channels guide](channels.html#using-token-authentication). With token authentication, you should access `socket.assigns.user_id`, set in `UserSocket`, instead of `socket.assigns.name` set from parameters. ## [](presence.html#usage-with-liveview)Usage With LiveView Whilst Phoenix does ship with a JavaScript API for dealing with presence, it is also possible to extend the `HelloWeb.Presence` module to support [LiveView](../phoenix_live_view.html). One thing to keep in mind when dealing with LiveView, is that each LiveView is a stateful process, so if we keep the presence state in the LiveView, each LiveView process will contain the full list of online users in memory. Instead, we can keep track of the online users within the `Presence` process, and pass separate events to the LiveView, which can use a stream to update the online list. To start with, we need to update the `lib/hello_web/channels/presence.ex` file to add some optional callbacks to the `HelloWeb.Presence` module. Firstly, we add the `init/1` callback. This allows us to keep track of the presence state within the process. ``` def init(_opts) do {:ok, %{}} end ``` The presence module also allows a `fetch/2` callback, this allows the data fetched from the presence to be modified, allowing us to define the shape of the response. In this case we are adding an `id` and a `user` map. ``` def fetch(_topic, presences) do for {key, %{metas: [meta | metas]}} <- presences, into: %{} do # user can be populated here from the database here we populate # the name for demonstration purposes {key, %{metas: [meta | metas], id: meta.id, user: %{name: meta.id}}} end end ``` The final thing to add is the `handle_metas/4` callback. This callback updates the state that we keep track of in `HelloWeb.Presence` based on the user leaves and joins. ``` def handle_metas(topic, %{joins: joins, leaves: leaves}, presences, state) do for {user_id, presence} <- joins do user_data = %{id: user_id, user: presence.user, metas: Map.fetch!(presences, user_id)} msg = {__MODULE__, {:join, user_data}} Phoenix.PubSub.local_broadcast(Hello.PubSub, "proxy:#{topic}", msg) end for {user_id, presence} <- leaves do metas = case Map.fetch(presences, user_id) do {:ok, presence_metas} -> presence_metas :error -> [] end user_data = %{id: user_id, user: presence.user, metas: metas} msg = {__MODULE__, {:leave, user_data}} Phoenix.PubSub.local_broadcast(Hello.PubSub, "proxy:#{topic}", msg) end {:ok, state} end ``` You can see that we are broadcasting events for the joins and leaves. These will be listened to by the LiveView process. You'll also see that we use "proxy" channel when broadcasting the joins and leaves. This is because we don't want our LiveView process to receive the presence events directly. We can add a few helper functions so that this particular implementation detail is abstracted from the LiveView module. ``` def list_online_users(), do: list("online_users") |> Enum.map(fn {_id, presence} -> presence end) def track_user(name, params), do: track(self(), "online_users", name, params) def subscribe(), do: Phoenix.PubSub.subscribe(Hello.PubSub, "proxy:online_users") ``` Now that we have our presence module set up and broadcasting events, we can create a LiveView. Create a new file `lib/hello_web/live/online/index.ex` with the following contents: ``` defmodule HelloWeb.OnlineLive do use HelloWeb, :live_view def mount(params, _session, socket) do socket = stream(socket, :presences, []) socket = if connected?(socket) do HelloWeb.Presence.track_user(params["name"], %{id: params["name"]}) HelloWeb.Presence.subscribe() stream(socket, :presences, HelloWeb.Presence.list_online_users()) else socket end {:ok, socket} end def render(assigns) do ~H""" <ul id="online_users" phx-update="stream"> <li :for={{dom_id, %{id: id, metas: metas}} <- @streams.presences} id={dom_id}>{id} ({length(metas)})</li> </ul> """ end def handle_info({HelloWeb.Presence, {:join, presence}}, socket) do {:noreply, stream_insert(socket, :presences, presence)} end def handle_info({HelloWeb.Presence, {:leave, presence}}, socket) do if presence.metas == [] do {:noreply, stream_delete(socket, :presences, presence)} else {:noreply, stream_insert(socket, :presences, presence)} end end end ``` If we add this route to the `lib/hello_web/router.ex`: ``` live "/online/:name", OnlineLive, :index ``` Then we can navigate to [http://localhost:4000/online/Alice](http://localhost:4000/online/Alice) in one tab, and [http://localhost:4000/online/Bob](http://localhost:4000/online/Bob) in another, you'll see that the presences are tracked, along with the number of presences per user. Opening and closing tabs with various users will update the presence list in real-time. [← Previous Page Channels](channels.html) [Next Page → Introduction to Testing](testing.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/real_time/presence.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/releases.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/deployment/releases.md#L1 "View Source") Deploying with Releases ## [](releases.html#what-we-ll-need)What we'll need The only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](up_and_running.html). ## [](releases.html#goals)Goals Our main goal for this guide is to package your Phoenix application into a self-contained directory that includes the Erlang VM, Elixir, all of your code and dependencies. This package can then be dropped into a production machine. ## [](releases.html#releases-assemble)Releases, assemble! If you are not familiar with Elixir releases yet, we recommend you to read [Elixir's excellent docs](../mix/Mix.Tasks.Release.html) before continuing. Once that is done, you can assemble a release by going through all of the steps in our general [deployment guide](deployment.html) with [`mix release`](../mix/Mix.Tasks.Release.html) at the end. Let's recap. First set the environment variables: ``` $ mix phx.gen.secret REALLY_LONG_SECRET $ export SECRET_KEY_BASE=REALLY_LONG_SECRET $ export DATABASE_URL=ecto://USER:PASS@HOST/database ``` Then load dependencies to compile code and assets: ``` # Initial setup $ mix deps.get --only prod $ MIX_ENV=prod mix compile # Compile assets $ MIX_ENV=prod mix assets.deploy ``` And now run [`mix phx.gen.release`](Mix.Tasks.Phx.Gen.Release.html): ``` $ mix phx.gen.release ==> my_app * creating rel/overlays/bin/server * creating rel/overlays/bin/server.bat * creating rel/overlays/bin/migrate * creating rel/overlays/bin/migrate.bat * creating lib/my_app/release.ex Your application is ready to be deployed in a release! # To start your system _build/dev/rel/my_app/bin/my_app start # To start your system with the Phoenix server running _build/dev/rel/my_app/bin/server # To run migrations _build/dev/rel/my_app/bin/migrate Once the release is running: # To connect to it remotely _build/dev/rel/my_app/bin/my_app remote # To stop it gracefully (you may also send SIGINT/SIGTERM) _build/dev/rel/my_app/bin/my_app stop To list all commands: _build/dev/rel/my_app/bin/my_app ``` The `phx.gen.release` task generated a few files for us to assist in releases. First, it created `server` and `migrate` *overlay* scripts for conveniently running the phoenix server inside a release or invoking migrations from a release. The files in the `rel/overlays` directory are copied into every release environment. Next, it generated a `release.ex` file which is used to invoke Ecto migrations without a dependency on `mix` itself. *Note*: If you are a Docker user, you can pass the `--docker` flag to [`mix phx.gen.release`](Mix.Tasks.Phx.Gen.Release.html) to generate a Dockerfile ready for deployment. Next, we can invoke [`mix release`](../mix/Mix.Tasks.Release.html) to build the release: ``` $ MIX_ENV=prod mix release Generated my_app app * assembling my_app-0.1.0 on MIX_ENV=prod * using config/runtime.exs to configure the release at runtime Release created at _build/prod/rel/my_app! # To start your system _build/prod/rel/my_app/bin/my_app start ... ``` You can start the release by calling `_build/prod/rel/my_app/bin/my_app start`, or boot your webserver by calling `_build/prod/rel/my_app/bin/server`, where you have to replace `my_app` by your current application name. Now you can get all of the files under the `_build/prod/rel/my_app` directory, package it, and run it in any production machine with the same OS and architecture as the one that assembled the release. For more details, check the [docs for `mix release`](../mix/Mix.Tasks.Release.html). But before we finish this guide, there is one more feature from releases that most Phoenix application will use, so let's talk about that. ## [](releases.html#ecto-migrations-and-custom-commands)Ecto migrations and custom commands A common need in production systems is to execute custom commands required to set up the production environment. One of such commands is precisely migrating the database. Since we don't have [`Mix`](../mix/Mix.html), a *build* tool, inside releases, which are a production artifact, we need to bring said commands directly into the release. The `phx.gen.release` command created the following `release.ex` file in your project `lib/my_app/release.ex`, with the following content: ``` defmodule MyApp.Release do @app :my_app def migrate do load_app() for repo <- repos() do {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) end end def rollback(repo, version) do load_app() {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) end defp repos do Application.fetch_env!(@app, :ecto_repos) end defp load_app do Application.load(@app) end end ``` Where you replace the first two lines by your application names. Now you can assemble a new release with `MIX_ENV=prod mix release` and you can invoke any code, including the functions in the module above, by calling the `eval` command: ``` $ _build/prod/rel/my_app/bin/my_app eval "MyApp.Release.migrate" ``` And that's it! If you peek inside the `migrate` script, you'll see it wraps exactly this invocation. You can use this approach to create any custom command to run in production. In this case, we used `load_app`, which calls [`Application.load/1`](../elixir/Application.html#load/1) to load the current application without starting it. However, you may want to write a custom command that starts the whole application. In such cases, [`Application.ensure_all_started/1`](../elixir/Application.html#ensure_all_started/1) must be used. Keep in mind, starting the application will start all processes for the current application, including the Phoenix endpoint. This can be circumvented by changing your supervision tree to not start certain children under certain conditions. For example, in the release commands file you could do: ``` defp start_app do load_app() Application.put_env(@app, :minimal, true) Application.ensure_all_started(@app) end ``` And then in your application you check `Application.get_env(@app, :minimal)` and start only part of the children when it is set. ## [](releases.html#containers)Containers Elixir releases work well with container technologies, such as Docker. The idea is that you assemble the release inside the Docker container and then build an image based on the release artifacts. If you call `mix phx.gen.release --docker` you'll see a new file with these contents: ``` # Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian # instead of Alpine to avoid DNS resolution issues in production. # # https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu # https://hub.docker.com/_/ubuntu?tab=tags # # This file is based on these images: # # - https://hub.docker.com/r/hexpm/elixir/tags - for the build image # - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20230612-slim - for the release image # - https://pkgs.org/ - resource for finding needed packages # - Ex: hexpm/elixir:1.14.5-erlang-25.3.2.4-debian-bullseye-20230612-slim # ARG ELIXIR_VERSION=1.14.5 ARG OTP_VERSION=25.3.2.4 ARG DEBIAN_VERSION=bullseye-20230612-slim ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" FROM ${BUILDER_IMAGE} as builder # install build dependencies RUN apt-get update -y && apt-get install -y build-essential git \ && apt-get clean && rm -f /var/lib/apt/lists/*_* # prepare build dir WORKDIR /app # install hex + rebar RUN mix local.hex --force && \ mix local.rebar --force # set build ENV ENV MIX_ENV="prod" # install mix dependencies COPY mix.exs mix.lock ./ RUN mix deps.get --only $MIX_ENV RUN mkdir config # copy compile-time config files before we compile dependencies # to ensure any relevant config change will trigger the dependencies # to be re-compiled. COPY config/config.exs config/${MIX_ENV}.exs config/ RUN mix deps.compile COPY priv priv COPY lib lib COPY assets assets # compile assets RUN mix assets.deploy # Compile the release RUN mix compile # Changes to config/runtime.exs don't require recompiling the code COPY config/runtime.exs config/ COPY rel rel RUN mix release # start a new build stage so that the final image will only contain # the compiled release and other runtime necessities FROM ${RUNNER_IMAGE} RUN apt-get update -y && \ apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \ && apt-get clean && rm -f /var/lib/apt/lists/*_* # Set the locale RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 WORKDIR "/app" RUN chown nobody /app # set runner ENV ENV MIX_ENV="prod" # Only copy the final release from the build stage COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/my_app ./ USER nobody # If using an environment that doesn't automatically reap zombie processes, it is # advised to add an init process such as tini via `apt-get install` # above and adding an entrypoint. See https://github.com/krallin/tini for details # ENTRYPOINT ["/tini", "--"] CMD ["/app/bin/server"] ``` Where `my_app` is the name of your app. At the end, you will have an application in `/app` ready to run as `/app/bin/server`. A few points about configuring a containerized application: - If you run your app in a container, the `Endpoint` needs to be configured to listen on a "public" `:ip` address (like `0.0.0.0`) so that the app can be reached from outside the container. Whether the host should publish the container's ports to its own public IP or to localhost depends on your needs. - The more configuration you can provide at runtime (using `config/runtime.exs`), the more reusable your images will be across environments. In particular, secrets like database credentials and API keys should not be compiled into the image, but rather should be provided when creating containers based on that image. This is why the `Endpoint`'s `:secret_key_base` is configured in `config/runtime.exs` by default. - If possible, any environment variables that are needed at runtime should be read in `config/runtime.exs`, not scattered throughout your code. Having them all visible in one place will make it easier to ensure the containers get what they need, especially if the person doing the infrastructure work does not work on the Elixir code. Libraries in particular should never directly read environment variables; all their configuration should be handed to them by the top-level application, preferably [without using the application environment](../elixir/library-guidelines.html#avoid-application-configuration). [← Previous Page Introduction to Deployment](deployment.html) [Next Page → Deploying on Gigalixir](gigalixir.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/deployment/releases.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/request_lifecycle.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/request_lifecycle.md#L1 "View Source") Request life-cycle > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). The goal of this guide is to talk about Phoenix's request life-cycle. This guide will take a practical approach where we will learn by doing: we will add two new pages to our Phoenix project and comment on how the pieces fit together along the way. Let's get on with our first new Phoenix page! ## [](request_lifecycle.html#adding-a-new-page)Adding a new page When your browser accesses [http://localhost:4000/](http://localhost:4000/), it sends a HTTP request to whatever service is running on that address, in this case our Phoenix application. The HTTP request is made of a verb and a path. For example, the following browser requests translate into: Browser address barVerbPath[http://localhost:4000/](http://localhost:4000/)GET/[http://localhost:4000/hello](http://localhost:4000/hello)GET/hello[http://localhost:4000/hello/world](http://localhost:4000/hello/world)GET/hello/world There are other HTTP verbs. For example, submitting a form typically uses the POST verb. Web applications typically handle requests by mapping each verb/path pair into a specific part of your application. This matching in Phoenix is done by the router. For example, we may map "/articles" to a portion of our application that shows all articles. Therefore, to add a new page, our first task is to add a new route. ### [](request_lifecycle.html#a-new-route)A new route The router maps unique HTTP verb/path pairs to controller/action pairs which will handle them. Controllers in Phoenix are simply Elixir modules. Actions are functions that are defined within these controllers. Phoenix generates a router file for us in new applications at `lib/hello_web/router.ex`. This is where we will be working for this section. The route for our "Welcome to Phoenix!" page from the previous [Up And Running Guide](up_and_running.html) looks like this. ``` get "/", PageController, :home ``` Let's digest what this route is telling us. Visiting [http://localhost:4000/](http://localhost:4000/) issues an HTTP `GET` request to the root path. All requests like this will be handled by the `home/2` function in the `HelloWeb.PageController` module defined in `lib/hello_web/controllers/page_controller.ex`. The page we are going to build will say "Hello World, from Phoenix!" when we point our browser to [http://localhost:4000/hello](http://localhost:4000/hello). The first thing we need to do is to create the page route for a new page. Let's open up `lib/hello_web/router.ex` in a text editor. For a brand new application, it looks like this: ``` defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end scope "/", HelloWeb do pipe_through :browser get "/", PageController, :home end # Other scopes may use custom stacks. # scope "/api", HelloWeb do # pipe_through :api # end # ... end ``` For now, we'll ignore the pipelines and the use of `scope` here and just focus on adding a route. We will discuss those in the [Routing guide](routing.html). Let's add a new route to the router that maps a `GET` request for `/hello` to the `index` action of a soon-to-be-created `HelloWeb.HelloController` inside the `scope "/" do` block of the router: ``` scope "/", HelloWeb do pipe_through :browser get "/", PageController, :home get "/hello", HelloController, :index end ``` ### [](request_lifecycle.html#a-new-controller)A new controller Controllers are Elixir modules, and actions are Elixir functions defined in them. The purpose of actions is to gather the data and perform the tasks needed for rendering. Our route specifies that we need a `HelloWeb.HelloController` module with an `index/2` function. To make the `index` action happen, let's create a new `lib/hello_web/controllers/hello_controller.ex` file, and make it look like the following: ``` defmodule HelloWeb.HelloController do use HelloWeb, :controller def index(conn, _params) do render(conn, :index) end end ``` We'll save a discussion of `use HelloWeb, :controller` for the [Controllers guide](controllers.html). For now, let's focus on the `index` action. All controller actions take two arguments. The first is `conn`, a struct which holds a ton of data about the request. The second is `params`, which are the request parameters. Here, we are not using `params`, and we avoid compiler warnings by prefixing it with `_`. The core of this action is `render(conn, :index)`. It tells Phoenix to render the `index` template. The modules responsible for rendering are called views. By default, Phoenix views are named after the controller (`HelloController`) and format (`HTML` in this case), so Phoenix is expecting a `HelloWeb.HelloHTML` to exist and define an `index/1` function. ### [](request_lifecycle.html#a-new-view)A new view Phoenix views act as the presentation layer. For example, we expect the output of rendering `index` to be a complete HTML page. To make our lives easier, we often use templates for creating those HTML pages. Let's create a new view. Create `lib/hello_web/controllers/hello_html.ex` and make it look like this: ``` defmodule HelloWeb.HelloHTML do use HelloWeb, :html end ``` To add templates to this view, we can define them as function components in the module or in separate files. Let's start by defining a function component: ``` defmodule HelloWeb.HelloHTML do use HelloWeb, :html def index(assigns) do ~H""" Hello! """ end end ``` We defined a function that receives `assigns` as arguments and use [the `~H` sigil](../phoenix_live_view/Phoenix.Component.html#sigil_H/2) to put the contents we want to render. Inside the `~H` sigil, we use a templating language called HEEx, which stands for "HTML+EEx". [`EEx`](../eex/EEx.html) is a library for embedding Elixir that ships as part of Elixir itself. "HTML+EEx" is a Phoenix extension of EEx that is HTML aware, with support for HTML validation, components, and automatic escaping of values. The latter protects you from security vulnerabilities like Cross-Site-Scripting with no extra work on your part. A template file works in the same way. Function components are great for smaller templates and separate files are a good choice when you have a lot of markup or your functions start to feel unmanageable. Let's give it a try by defining a template in its own file. First delete our `def index(assigns)` function from above and replace it with an `embed_templates` declaration: ``` defmodule HelloWeb.HelloHTML do use HelloWeb, :html embed_templates "hello_html/*" end ``` Here we are telling `Phoenix.Component` to embed all `.heex` templates found in the sibling `hello_html` directory into our module as function definitions. Next, we need to add files to the `lib/hello_web/controllers/hello_html` directory. Note the controller name (`HelloController`), the view name (`HelloHTML`), and the template directory (`hello_html`) all follow the same naming convention and are named after each other. They are also collocated together in the directory tree: > **Note**: We can rename the `hello_html` directory to whatever we want and put it in a subdirectory of `lib/hello_web/controllers`, as long as we update the `embed_templates` setting accordingly. However, it's best to keep the same naming convention to prevent any confusion. ``` lib/hello_web ├── controllers │ ├── hello_controller.ex │ ├── hello_html.ex │ ├── hello_html | ├── index.html.heex ``` A template file has the following structure: `NAME.FORMAT.TEMPLATING_LANGUAGE`. In our case, let's create an `index.html.heex` file at `lib/hello_web/controllers/hello_html/index.html.heex`: ``` <section> <h2>Hello World, from Phoenix!</h2> </section> ``` Template files are compiled into the module as function components themselves, there is no runtime or performance difference between the two styles. Now that we've got the route, controller, view, and template, we should be able to point our browsers at [http://localhost:4000/hello](http://localhost:4000/hello) and see our greeting from Phoenix! (In case you stopped the server along the way, the task to restart it is [`mix phx.server`](Mix.Tasks.Phx.Server.html).) ![Phoenix Greets Us](assets/images/hello-from-phoenix.png) There are a couple of interesting things to notice about what we just did. We didn't need to stop and restart the server while we made these changes. Yes, Phoenix has hot code reloading! Also, even though our `index.html.heex` file consists of only a single `section` tag, the page we get is a full HTML document. Our index template is actually rendered into layouts: first it renders `lib/hello_web/components/layouts/root.html.heex` which renders `lib/hello_web/components/layouts/app.html.heex` which finally includes our contents. If you open those files, you'll see a line that looks like this at the bottom: ``` {@inner_content} ``` Which injects our template into the layout before the HTML is sent off to the browser. We will talk more about layouts in the Controllers guide. > A note on hot code reloading: Some editors with their automatic linters may prevent hot code reloading from working. If it's not working for you, please see the discussion in [this issue](https://github.com/phoenixframework/phoenix/issues/1165). ## [](request_lifecycle.html#from-endpoint-to-views)From endpoint to views As we built our first page, we could start to understand how the request life-cycle is put together. Now let's take a more holistic look at it. All HTTP requests start in our application endpoint. You can find it as a module named `HelloWeb.Endpoint` in `lib/hello_web/endpoint.ex`. Once you open up the endpoint file, you will see that, similar to the router, the endpoint has many calls to `plug`. [`Plug`](../plug/1.15.3/Plug.html) is a library and a specification for stitching web applications together. It is an essential part of how Phoenix handles requests and we will discuss it in detail in the [Plug guide](plug.html) coming next. For now, it suffices to say that each plug defines a slice of request processing. In the endpoint you will find a skeleton roughly like this: ``` defmodule HelloWeb.Endpoint do use Phoenix.Endpoint, otp_app: :hello plug Plug.Static, ... plug Plug.RequestId plug Plug.Telemetry, ... plug Plug.Parsers, ... plug Plug.MethodOverride plug Plug.Head plug Plug.Session, ... plug HelloWeb.Router end ``` Each of these plugs have a specific responsibility that we will learn later. The last plug is precisely the `HelloWeb.Router` module. This allows the endpoint to delegate all further request processing to the router. As we now know, its main responsibility is to map verb/path pairs to controllers. The controller then tells a view to render a template. At this moment, you may be thinking this can be a lot of steps to simply render a page. However, as our application grows in complexity, we will see that each layer serves a distinct purpose: - endpoint ([`Phoenix.Endpoint`](Phoenix.Endpoint.html)) - the endpoint contains the common and initial path that all requests go through. If you want something to happen on all requests, it goes to the endpoint. - router ([`Phoenix.Router`](Phoenix.Router.html)) - the router is responsible for dispatching verb/path to controllers. The router also allows us to scope functionality. For example, some pages in your application may require user authentication, others may not. - controller ([`Phoenix.Controller`](Phoenix.Controller.html)) - the job of the controller is to retrieve request information, talk to your business domain, and prepare data for the presentation layer. - view - the view handles the structured data from the controller and converts it to a presentation to be shown to users. Views are often named after the content format they are rendering. Let's do a quick recap on how the last three components work together by adding another page. ## [](request_lifecycle.html#another-new-page)Another new page Let's add just a little complexity to our application. We're going to add a new page that will recognize a piece of the URL, label it as a "messenger" and pass it through the controller into the template so our messenger can say hello. As we did last time, the first thing we'll do is create a new route. ### [](request_lifecycle.html#another-new-route)Another new route For this exercise, we're going to reuse `HelloController` created at the [previous step](request_lifecycle.html#a-new-controller) and add a new `show` action. We'll add a line just below our last route, like this: ``` scope "/", HelloWeb do pipe_through :browser get "/", PageController, :home get "/hello", HelloController, :index get "/hello/:messenger", HelloController, :show end ``` Notice that we use the `:messenger` syntax in the path. Phoenix will take whatever value that appears in that position in the URL and convert it into a parameter. For example, if we point the browser at: `http://localhost:4000/hello/Frank`, the value of `"messenger"` will be `"Frank"`. ### [](request_lifecycle.html#another-new-action)Another new action Requests to our new route will be handled by the `HelloWeb.HelloController` `show` action. We already have the controller at `lib/hello_web/controllers/hello_controller.ex`, so all we need to do is edit that controller and add a `show` action to it. This time, we'll need to extract the messenger from the parameters so that we can pass it (the messenger) to the template. To do that, we add this show function to the controller: ``` def show(conn, %{"messenger" => messenger}) do render(conn, :show, messenger: messenger) end ``` Within the body of the `show` action, we also pass a third argument to the render function, a key-value pair where `:messenger` is the key, and the `messenger` variable is passed as the value. If the body of the action needs access to the full map of parameters bound to the `params` variable, in addition to the bound messenger variable, we could define `show/2` like this: ``` def show(conn, %{"messenger" => messenger} = params) do ... end ``` It's good to remember that the keys of the `params` map will always be strings, and that the equals sign does not represent assignment, but is instead a [pattern match](../elixir/pattern-matching.html) assertion. ### [](request_lifecycle.html#another-new-template)Another new template For the last piece of this puzzle, we'll need a new template. Since it is for the `show` action of `HelloController`, it will go into the `lib/hello_web/controllers/hello_html` directory and be called `show.html.heex`. It will look surprisingly like our `index.html.heex` template, except that we will need to display the name of our messenger. To do that, we'll use the special HEEx tags for executing Elixir expressions: `{...}` and `<%= %>`. Notice that EEx tag has an equals sign like this: `<%=` . That means that any Elixir code that goes between those tags will be executed, and the resulting value will replace the tag in the HTML output. If the equals sign were missing, the code would still be executed, but the value would not appear on the page. Remember our templates are written in HEEx (HTML+EEx). HEEx is a superset of EEx, and thereby supports the EEx `<%= %>` interpolation syntax for interpolating arbitrary blocks of code. In general, the HEEx `{...}` interpolation syntax is preferred anytime there is HTML-aware intepolation to be done – such as within attributes or inline values with a body. The only times [`EEx`](../eex/EEx.html) `<%= %>` interpolation is necessary is for interpolationg arbitrary blocks of markup, such as branching logic that inects separate markup trees, or for interpolating values within `<script>` or `<style>` tags. This is what the `hello_html/show.html.heex` template should look like: ``` <section> <h2>Hello World, from {@messenger}!</h2> </section> ``` Our messenger appears as `@messenger`. The values we passed to the view from the controller are collectively called our "assigns". We could access our messenger value via `assigns.messenger` but through some metaprogramming, Phoenix gives us the much cleaner `@` syntax for use in templates. We're done. If you point your browser to [http://localhost:4000/hello/Frank](http://localhost:4000/hello/Frank), you should see a page that looks like this: ![Frank Greets Us from Phoenix](assets/images/hello-world-from-frank.png) Play around a bit. Whatever you put after `/hello/` will appear on the page as your messenger. [← Previous Page Directory structure](directory_structure.html) [Next Page → Plug](plug.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/request_lifecycle.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/router.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/cheatsheets/router.cheatmd#L1 "View Source") Print cheatsheet Routing cheatsheet > Those need to be declared in the correct router module and scope. A quick reference to the common routing features' syntax. For an exhaustive overview, refer to the [routing guides](routing.html). ## [](router.html#routing-declaration)Routing declaration ### [](router.html#single-route)Single route ``` get "/users", UserController, :index patch "/users/:id", UserController, :update ``` ``` # generated routes ~p"/users" ~p"/users/9" # user_id is 9 ``` Also accepts `put`, `patch`, `options`, `delete` and `head`. ### [](router.html#resources)Resources #### Simple ``` resources "/users", UserController ``` Generates `:index`, `:edit`, `:new`, `:show`, `:create`, `:update` and `:delete`. #### Options ``` resources "/users", UserController, only: [:show] resources "/users", UserController, except: [:create, :delete] resources "/users", UserController, as: :person # ~p"/person" ``` #### Nested ``` resources "/users", UserController do resources "/posts", PostController end ``` ``` # generated routes ~p"/users/3/posts" # user_id is 3 ~p"/users/3/posts/17" # user_id is 3 and post_id = 17 ``` For more info check the [resources docs.](https://hexdocs.pm/phoenix/routing-1.html#resources) ### [](router.html#scopes)Scopes #### Simple ``` scope "/admin", HelloWeb.Admin do pipe_through :browser resources "/users", UserController end ``` ``` # generated path helpers ~p"/admin/users" ``` #### Nested ``` scope "/api", HelloWeb.Api, as: :api do pipe_through :api scope "/v1", V1, as: :v1 do resources "/users", UserController end end ``` ``` # generated path helpers ~p"/api/v1/users" ``` For more info check the [scoped routes](routing.html#scoped-routes) docs. [← Previous Page Deploying on Heroku](heroku.html) [Next Page → Custom Error Pages](custom_error_pages.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/cheatsheets/router.cheatmd)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/routing.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/routing.md#L1 "View Source") Routing > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html). Routers are the main hubs of Phoenix applications. They match HTTP requests to controller actions, wire up real-time channel handlers, and define a series of pipeline transformations scoped to a set of routes. The router file that Phoenix generates, `lib/hello_web/router.ex`, will look something like this one: ``` defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end scope "/", HelloWeb do pipe_through :browser get "/", PageController, :home end # Other scopes may use custom stacks. # scope "/api", HelloWeb do # pipe_through :api # end # ... end ``` Both the router and controller module names will be prefixed with the name you gave your application suffixed with `Web`. The first line of this module, `use HelloWeb, :router`, simply makes Phoenix router functions available in our particular router. Scopes have their own section in this guide, so we won't spend time on the `scope "/", HelloWeb do` block here. The `pipe_through :browser` line will get a full treatment in the "Pipelines" section of this guide. For now, you only need to know that pipelines allow a set of plugs to be applied to different sets of routes. Inside the scope block, however, we have our first actual route: ``` get "/", PageController, :home ``` `get` is a Phoenix macro that corresponds to the HTTP verb GET. Similar macros exist for other HTTP verbs, including POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE, and HEAD. #### Why the macros? Phoenix does its best to keep the usage of macros low. You may have noticed, however, that the [`Phoenix.Router`](Phoenix.Router.html) relies heavily on macros. Why is that? We use `get`, `post`, `put`, and `delete` to define your routes. We use macros for two purposes: - They define the routing engine, used on every request, to choose which controller to dispatch the request to. Thanks to macros, Phoenix compiles all of your routes to a huge case-statement with pattern matching rules, which is heavily optimized by the Erlang VM - For each route you define, we also define metadata to implement [`Phoenix.VerifiedRoutes`](Phoenix.VerifiedRoutes.html). As we will soon learn, verified routes allow us to reference any route as if it were a plain looking string, except that it is verified by the compiler to be valid (making it much harder to ship broken links, forms, mails, etc to production) In other words, the router relies on macros to build applications that are faster and safer. Also remember that macros in Elixir are compile-time only, which gives plenty of stability after the code is compiled. As we will learn next, Phoenix also provides introspection for all defined routes via [`mix phx.routes`](Mix.Tasks.Phx.Routes.html). ## [](routing.html#examining-routes)Examining routes Phoenix provides an excellent tool for investigating routes in an application: [`mix phx.routes`](Mix.Tasks.Phx.Routes.html). Let's see how this works. Go to the root of a newly-generated Phoenix application and run [`mix phx.routes`](Mix.Tasks.Phx.Routes.html). You should see something like the following, generated with all routes you currently have: ``` $ mix phx.routes GET / HelloWeb.PageController :home ... ``` The route above tells us that any HTTP GET request for the root of the application will be handled by the `home` action of the `HelloWeb.PageController`. ## [](routing.html#resources)Resources The router supports other macros besides those for HTTP verbs like [`get`](Phoenix.Router.html#get/3), [`post`](Phoenix.Router.html#post/3), and [`put`](Phoenix.Router.html#put/3). The most important among them is [`resources`](Phoenix.Router.html#resources/4). Let's add a resource to our `lib/hello_web/router.ex` file like this: ``` scope "/", HelloWeb do pipe_through :browser get "/", PageController, :home resources "/users", UserController ... end ``` For now it doesn't matter that we don't actually have a `HelloWeb.UserController`. Run [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) once again at the root of your project. You should see something like the following: ``` ... GET /users HelloWeb.UserController :index GET /users/:id/edit HelloWeb.UserController :edit GET /users/new HelloWeb.UserController :new GET /users/:id HelloWeb.UserController :show POST /users HelloWeb.UserController :create PATCH /users/:id HelloWeb.UserController :update PUT /users/:id HelloWeb.UserController :update DELETE /users/:id HelloWeb.UserController :delete ... ``` This is the standard matrix of HTTP verbs, paths, and controller actions. For a while, this was known as RESTful routes, but most consider this a misnomer nowadays. Let's look at them individually. - A GET request to `/users` will invoke the `index` action to show all the users. - A GET request to `/users/:id/edit` will invoke the `edit` action with an ID to retrieve an individual user from the data store and present the information in a form for editing. - A GET request to `/users/new` will invoke the `new` action to present a form for creating a new user. - A GET request to `/users/:id` will invoke the `show` action with an id to show an individual user identified by that ID. - A POST request to `/users` will invoke the `create` action to save a new user to the data store. - A PATCH request to `/users/:id` will invoke the `update` action with an ID to save the updated user to the data store. - A PUT request to `/users/:id` will also invoke the `update` action with an ID to save the updated user to the data store. - A DELETE request to `/users/:id` will invoke the `delete` action with an ID to remove the individual user from the data store. If we don't need all these routes, we can be selective using the `:only` and `:except` options to filter specific actions. Let's say we have a read-only posts resource. We could define it like this: ``` resources "/posts", PostController, only: [:index, :show] ``` Running [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) shows that we now only have the routes to the index and show actions defined. ``` GET /posts HelloWeb.PostController :index GET /posts/:id HelloWeb.PostController :show ``` Similarly, if we have a comments resource, and we don't want to provide a route to delete one, we could define a route like this. ``` resources "/comments", CommentController, except: [:delete] ``` Running [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) now shows that we have all the routes except the DELETE request to the delete action. ``` GET /comments HelloWeb.CommentController :index GET /comments/:id/edit HelloWeb.CommentController :edit GET /comments/new HelloWeb.CommentController :new GET /comments/:id HelloWeb.CommentController :show POST /comments HelloWeb.CommentController :create PATCH /comments/:id HelloWeb.CommentController :update PUT /comments/:id HelloWeb.CommentController :update ``` The [`Phoenix.Router.resources/4`](Phoenix.Router.html#resources/4) macro describes additional options for customizing resource routes. ## [](routing.html#verified-routes)Verified Routes Phoenix includes [`Phoenix.VerifiedRoutes`](Phoenix.VerifiedRoutes.html) module which provides compile-time checks of router paths against your router by using the `~p` sigil. For example, you can write paths in controllers, tests, and templates and the compiler will make sure those actually match routes defined in your router. Let's see it in action. Run `iex -S mix` at the root of the project. We'll define a throwaway example module that builds a couple `~p` route paths. ``` iex> defmodule RouteExample do ...> use HelloWeb, :verified_routes ...> ...> def example do ...> ~p"/comments" ...> ~p"/unknown/123" ...> end ...> end warning: no route path for HelloWeb.Router matches "/unknown/123" iex:5: RouteExample.example/0 {:module, RouteExample, ...} iex> ``` Notice how the first call to an existing route, `~p"/comments"` gave no warning, but a bad route path `~p"/unknown/123"` produced a compiler warning, just as it should. This is significant because it allows us to write otherwise hard-coded paths in our application and the compiler will let us know whenever we write a bad route or change our routing structure. Phoenix projects are set up out of the box to allow use of verified routes throughout your web layer, including tests. For example in your templates you can render `~p` links: ``` <.link href={~p"/"}>Welcome Page!</.link> <.link href={~p"/comments"}>View Comments</.link> ``` Or in a controller, issue a redirect: ``` redirect(conn, to: ~p"/comments/#{comment}") ``` Using `~p` for route paths ensures our application paths and URLs stay up to date with the router definitions. The compiler will catch bugs for us, and let us know when we change routes that are referenced elsewhere in our application. ### [](routing.html#more-on-verified-routes)More on verified routes What about paths with query strings? You can either add query string key values directly, or provide a dictionary of key-value pairs, for example: ``` ~p"/users/17?admin=true&active=false" "/users/17?admin=true&active=false" ~p"/users/17?#{[admin: true]}" "/users/17?admin=true" ``` What if we need a full URL instead of a path? Just wrap your path with a call to [`Phoenix.VerifiedRoutes.url/1`](Phoenix.VerifiedRoutes.html#url/1), which is imported everywhere that `~p` is available: ``` url(~p"/users") "http://localhost:4000/users" ``` The `url` calls will get the host, port, proxy port, and SSL information needed to construct the full URL from the configuration parameters set for each environment. We'll talk about configuration in more detail in its own guide. For now, you can take a look at `config/dev.exs` file in your own project to see those values. ## [](routing.html#nested-resources)Nested resources It is also possible to nest resources in a Phoenix router. Let's say we also have a `posts` resource that has a many-to-one relationship with `users`. That is to say, a user can create many posts, and an individual post belongs to only one user. We can represent that by adding a nested route in `lib/hello_web/router.ex` like this: ``` resources "/users", UserController do resources "/posts", PostController end ``` When we run [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) now, in addition to the routes we saw for `users` above, we get the following set of routes: ``` ... GET /users/:user_id/posts HelloWeb.PostController :index GET /users/:user_id/posts/:id/edit HelloWeb.PostController :edit GET /users/:user_id/posts/new HelloWeb.PostController :new GET /users/:user_id/posts/:id HelloWeb.PostController :show POST /users/:user_id/posts HelloWeb.PostController :create PATCH /users/:user_id/posts/:id HelloWeb.PostController :update PUT /users/:user_id/posts/:id HelloWeb.PostController :update DELETE /users/:user_id/posts/:id HelloWeb.PostController :delete ... ``` We see that each of these routes scopes the posts to a user ID. For the first one, we will invoke `PostController`'s `index` action, but we will pass in a `user_id`. This implies that we would display all the posts for that individual user only. The same scoping applies for all these routes. When building paths for nested routes, we will need to interpolate the IDs where they belong in route definition. For the following `show` route, `42` is the `user_id`, and `17` is the `post_id`. ``` user_id = 42 post_id = 17 ~p"/users/#{user_id}/posts/#{post_id}" "/users/42/posts/17" ``` Verified routes also support the [`Phoenix.Param`](Phoenix.Param.html) protocol, but we don't need to concern ourselves with Elixir protocols just yet. Just know that once we start building our application with structs like `%User{}` and `%Post{}`, we'll be able to interpolate those data structures directly into our `~p` paths and Phoenix will pluck out the correct fields to use in the route. ``` ~p"/users/#{user}/posts/#{post}" "/users/42/posts/17" ``` Notice how we didn't need to interpolate `user.id` or `post.id`? This is particularly nice if we decide later we want to make our URLs a little nicer and start using slugs instead. We don't need to change any of our `~p`'s! ## [](routing.html#scoped-routes)Scoped routes Scopes are a way to group routes under a common path prefix and scoped set of plugs. We might want to do this for admin functionality, APIs, and especially for versioned APIs. Let's say we have user-generated reviews on a site, and that those reviews first need to be approved by an administrator. The semantics of these resources are quite different, and they might not share the same controller. Scopes enable us to segregate these routes. The paths to the user-facing reviews would look like a standard resource. ``` /reviews /reviews/1234 /reviews/1234/edit ... ``` The administration review paths can be prefixed with `/admin`. ``` /admin/reviews /admin/reviews/1234 /admin/reviews/1234/edit ... ``` We accomplish this with a scoped route that sets a path option to `/admin` like this one. We can nest this scope inside another scope, but instead, let's set it by itself at the root, by adding to `lib/hello_web/router.ex` the following: ``` scope "/admin", HelloWeb.Admin do pipe_through :browser resources "/reviews", ReviewController end ``` We define a new scope where all routes are prefixed with `/admin` and all controllers are under the `HelloWeb.Admin` namespace. Running [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) again, in addition to the previous set of routes we get the following: ``` ... GET /admin/reviews HelloWeb.Admin.ReviewController :index GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit GET /admin/reviews/new HelloWeb.Admin.ReviewController :new GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show POST /admin/reviews HelloWeb.Admin.ReviewController :create PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete ... ``` This looks good, but there is a problem here. Remember that we wanted both user-facing review routes `/reviews` and the admin ones `/admin/reviews`. If we now include the user-facing reviews in our router under the root scope like this: ``` scope "/", HelloWeb do pipe_through :browser ... resources "/reviews", ReviewController end scope "/admin", HelloWeb.Admin do pipe_through :browser resources "/reviews", ReviewController end ``` and we run [`mix phx.routes`](Mix.Tasks.Phx.Routes.html), we get output for each scoped route: ``` ... GET /reviews HelloWeb.ReviewController :index GET /reviews/:id/edit HelloWeb.ReviewController :edit GET /reviews/new HelloWeb.ReviewController :new GET /reviews/:id HelloWeb.ReviewController :show POST /reviews HelloWeb.ReviewController :create PATCH /reviews/:id HelloWeb.ReviewController :update PUT /reviews/:id HelloWeb.ReviewController :update DELETE /reviews/:id HelloWeb.ReviewController :delete ... GET /admin/reviews HelloWeb.Admin.ReviewController :index GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit GET /admin/reviews/new HelloWeb.Admin.ReviewController :new GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show POST /admin/reviews HelloWeb.Admin.ReviewController :create PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete ``` What if we had a number of resources that were all handled by admins? We could put all of them inside the same scope like this: ``` scope "/admin", HelloWeb.Admin do pipe_through :browser resources "/images", ImageController resources "/reviews", ReviewController resources "/users", UserController end ``` Here's what [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) tells us: ``` ... GET /admin/images HelloWeb.Admin.ImageController :index GET /admin/images/:id/edit HelloWeb.Admin.ImageController :edit GET /admin/images/new HelloWeb.Admin.ImageController :new GET /admin/images/:id HelloWeb.Admin.ImageController :show POST /admin/images HelloWeb.Admin.ImageController :create PATCH /admin/images/:id HelloWeb.Admin.ImageController :update PUT /admin/images/:id HelloWeb.Admin.ImageController :update DELETE /admin/images/:id HelloWeb.Admin.ImageController :delete GET /admin/reviews HelloWeb.Admin.ReviewController :index GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit GET /admin/reviews/new HelloWeb.Admin.ReviewController :new GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show POST /admin/reviews HelloWeb.Admin.ReviewController :create PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete GET /admin/users HelloWeb.Admin.UserController :index GET /admin/users/:id/edit HelloWeb.Admin.UserController :edit GET /admin/users/new HelloWeb.Admin.UserController :new GET /admin/users/:id HelloWeb.Admin.UserController :show POST /admin/users HelloWeb.Admin.UserController :create PATCH /admin/users/:id HelloWeb.Admin.UserController :update PUT /admin/users/:id HelloWeb.Admin.UserController :update DELETE /admin/users/:id HelloWeb.Admin.UserController :delete ``` This is great, exactly what we want. Note how every route and controller is properly namespaced. Scopes can also be arbitrarily nested, but you should do it carefully as nesting can sometimes make our code confusing and less clear. With that said, suppose that we had a versioned API with resources defined for images, reviews, and users. Then technically, we could set up routes for the versioned API like this: ``` scope "/api", HelloWeb.Api, as: :api do pipe_through :api scope "/v1", V1, as: :v1 do resources "/images", ImageController resources "/reviews", ReviewController resources "/users", UserController end end ``` You can run [`mix phx.routes`](Mix.Tasks.Phx.Routes.html) to see how these definitions will look like. Interestingly, we can use multiple scopes with the same path as long as we are careful not to duplicate routes. The following router is perfectly fine with two scopes defined for the same path: ``` defmodule HelloWeb.Router do use Phoenix.Router ... scope "/", HelloWeb do pipe_through :browser resources "/users", UserController end scope "/", AnotherAppWeb do pipe_through :browser resources "/posts", PostController end ... end ``` If we do duplicate a route — which means two routes having the same path — we'll get this familiar warning: ``` warning: this clause cannot match because a previous clause at line 16 always matches ``` ## [](routing.html#pipelines)Pipelines We have come quite a long way in this guide without talking about one of the first lines we saw in the router: `pipe_through :browser`. It's time to fix that. Pipelines are a series of plugs that can be attached to specific scopes. If you are not familiar with plugs, we have an [in-depth guide about them](plug.html). Routes are defined inside scopes and scopes may pipe through multiple pipelines. Once a route matches, Phoenix invokes all plugs defined in all pipelines associated to that route. For example, accessing `/` will pipe through the `:browser` pipeline, consequently invoking all of its plugs. Phoenix defines two pipelines by default, `:browser` and `:api`, which can be used for a number of common tasks. In turn we can customize them as well as create new pipelines to meet our needs. ### [](routing.html#the-browser-and-api-pipelines)The `:browser` and `:api` pipelines As their names suggest, the `:browser` pipeline prepares for routes which render requests for a browser, and the `:api` pipeline prepares for routes which produce data for an API. The `:browser` pipeline has six plugs: The `plug :accepts, ["html"]` defines the accepted request format or formats. `:fetch_session`, which, naturally, fetches the session data and makes it available in the connection. `:fetch_live_flash`, which fetches any flash messages from LiveView and merges them with the controller flash messages. Then, the plug `:put_root_layout` will store the root layout for rendering purposes. Later `:protect_from_forgery` and `:put_secure_browser_headers`, protects form posts from cross-site forgery. Currently, the `:api` pipeline only defines `plug :accepts, ["json"]`. The router invokes a pipeline on a route defined within a scope. Routes outside of a scope have no pipelines. Although the use of nested scopes is discouraged (see above the versioned API example), if we call `pipe_through` within a nested scope, the router will invoke all `pipe_through`'s from parent scopes, followed by the nested one. Those are a lot of words bunched up together. Let's take a look at some examples to untangle their meaning. Here's another look at the router from a newly generated Phoenix application, this time with the `/api` scope uncommented back in and a route added. ``` defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :api do plug :accepts, ["json"] end scope "/", HelloWeb do pipe_through :browser get "/", PageController, :home end # Other scopes may use custom stacks. scope "/api", HelloWeb do pipe_through :api resources "/reviews", ReviewController end # ... end ``` When the server accepts a request, the request will always first pass through the plugs in our endpoint, after which it will attempt to match on the path and HTTP verb. Let's say that the request matches our first route: a GET to `/`. The router will first pipe that request through the `:browser` pipeline - which will fetch the session data, fetch the flash, and execute forgery protection - before it dispatches the request to `PageController`'s `home` action. Conversely, suppose the request matches any of the routes defined by the [`resources/2`](Phoenix.Router.html#resources/2) macro. In that case, the router will pipe it through the `:api` pipeline — which currently only performs content negotiation — before it dispatches further to the correct action of the `HelloWeb.ReviewController`. If no route matches, no pipeline is invoked and a 404 error is raised. ### [](routing.html#creating-new-pipelines)Creating new pipelines Phoenix allows us to create our own custom pipelines anywhere in the router. To do so, we call the [`pipeline/2`](Phoenix.Router.html#pipeline/2) macro with these arguments: an atom for the name of our new pipeline and a block with all the plugs we want in it. ``` defmodule HelloWeb.Router do use HelloWeb, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {HelloWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers end pipeline :auth do plug HelloWeb.Authentication end scope "/reviews", HelloWeb do pipe_through [:browser, :auth] resources "/", ReviewController end end ``` The above assumes there is a plug called `HelloWeb.Authentication` that performs authentication and is now part of the `:auth` pipeline. Note that pipelines themselves are plugs, so we can plug a pipeline inside another pipeline. For example, we could rewrite the `auth` pipeline above to automatically invoke `browser`, simplifying the downstream pipeline call: ``` pipeline :auth do plug :browser plug :ensure_authenticated_user plug :ensure_user_owns_review end scope "/reviews", HelloWeb do pipe_through :auth resources "/", ReviewController end ``` ## [](routing.html#how-to-organize-my-routes)How to organize my routes? In Phoenix, we tend to define several pipelines, that provide specific functionality. For example, the `:browser` and `:api` pipelines are meant to be accessed by specific clients, browsers and http clients respectively. Perhaps more importantly, it is also very common to define pipelines specific to authentication and authorization. For example, you might have a pipeline that requires all users are authenticated. Another pipeline may enforce only admin users can access certain routes. Once your pipelines are defined, you reuse the pipelines in the desired scopes, grouping your routes around their pipelines. For example, going back to our reviews example. Let's say anyone can read a review, but only authenticated users can create them. Your routes could look like this: ``` pipeline :browser do ... end pipeline :auth do plug HelloWeb.Authentication end scope "/" do pipe_through [:browser] get "/reviews", PostController, :index get "/reviews/:id", PostController, :show end scope "/" do pipe_through [:browser, :auth] get "/reviews/new", PostController, :new post "/reviews", PostController, :create end ``` Note in the above how the routes are split across different scopes. While the separation can be confusing at first, it has one big upside: it is very easy to inspect your routes and see all routes that, for example, require authentication and which ones do not. This helps with auditing and making sure your routes have the proper scope. You can create as few or as many scopes as you want. Because pipelines are reusable across scopes, they help encapsulate common functionality and you can compose them as necessary on each scope you define. ## [](routing.html#forward)Forward The [`Phoenix.Router.forward/4`](Phoenix.Router.html#forward/4) macro can be used to send all requests that start with a particular path to a particular plug. Let's say we have a part of our system that is responsible (it could even be a separate application or library) for running jobs in the background, it could have its own web interface for checking the status of the jobs. We can forward to this admin interface using: ``` defmodule HelloWeb.Router do use HelloWeb, :router ... scope "/", HelloWeb do ... end forward "/jobs", BackgroundJob.Plug end ``` This means that all routes starting with `/jobs` will be sent to the `HelloWeb.BackgroundJob.Plug` module. Inside the plug, you can match on subroutes, such as `/pending` and `/active` that shows the status of certain jobs. We can even mix the [`forward/4`](Phoenix.Router.html#forward/4) macro with pipelines. If we wanted to ensure that the user was authenticated and was an administrator in order to see the jobs page, we could use the following in our router. ``` defmodule HelloWeb.Router do use HelloWeb, :router ... scope "/" do pipe_through [:authenticate_user, :ensure_admin] forward "/jobs", BackgroundJob.Plug end end ``` This means the plugs in the `authenticate_user` and `ensure_admin` pipelines will be called before the `BackgroundJob.Plug` allowing them to send an appropriate response and halt the request accordingly. The `opts` that are received in the `init/1` callback of the Module Plug can be passed as a third argument. For example, maybe the background job lets you set the name of your application to be displayed on the page. This could be passed with: ``` forward "/jobs", BackgroundJob.Plug, name: "Hello Phoenix" ``` There is a fourth `router_opts` argument that can be passed. These options are outlined in the [`Phoenix.Router.scope/2`](Phoenix.Router.html#scope/2) documentation. `BackgroundJob.Plug` can be implemented as any Module Plug discussed in the [Plug guide](plug.html). Note though it is not advised to forward to another Phoenix endpoint. This is because plugs defined by your app and the forwarded endpoint would be invoked twice, which may lead to errors. ## [](routing.html#summary)Summary Routing is a big topic, and we have covered a lot of ground here. The important points to take away from this guide are: - Routes which begin with an HTTP verb name expand to a single clause of the match function. - Routes declared with `resources` expand to 8 clauses of the match function. - Resources may restrict the number of match function clauses by using the `only:` or `except:` options. - Any of these routes may be nested. - Any of these routes may be scoped to a given path. - Using verified routes with `~p` for compile-time route checks [← Previous Page Plug](plug.html) [Next Page → Controllers](controllers.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/routing.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/telemetry.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/telemetry.md#L1 "View Source") Telemetry In this guide, we will show you how to instrument and report on `:telemetry` events in your Phoenix application. > `te·lem·e·try` - the process of recording and transmitting the readings of an instrument. As you follow along with this guide, we will introduce you to the core concepts of Telemetry, you will initialize a reporter to capture your application's events as they occur, and we will guide you through the steps to properly instrument your own functions using `:telemetry`. Let's take a closer look at how Telemetry works in your application. ## [](telemetry.html#overview)Overview The `[:telemetry]` library allows you to emit events at various stages of an application's lifecycle. You can then respond to these events by, among other things, aggregating them as metrics and sending the metrics data to a reporting destination. Telemetry stores events by their name in an ETS table, along with the handler for each event. Then, when a given event is executed, Telemetry looks up its handler and invokes it. Phoenix's Telemetry tooling provides you with a supervisor that uses [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) to define the list of Telemetry events to handle and how to handle those events, i.e. how to structure them as a certain type of metric. This supervisor works together with Telemetry reporters to respond to the specified Telemetry events by aggregating them as the appropriate metric and sending them to the correct reporting destination. ## [](telemetry.html#the-telemetry-supervisor)The Telemetry supervisor Since v1.5, new Phoenix applications are generated with a Telemetry supervisor. This module is responsible for managing the lifecycle of your Telemetry processes. It also defines a `metrics/0` function, which returns a list of [`Telemetry.Metrics`](../telemetry_metrics.html) that you define for your application. By default, the supervisor also starts [`:telemetry_poller`](../telemetry_poller.html). By simply adding `:telemetry_poller` as a dependency, you can receive VM-related events on a specified interval. If you are coming from an older version of Phoenix, install the `:telemetry_metrics` and `:telemetry_poller` packages: ``` {:telemetry_metrics, "~> 1.0"}, {:telemetry_poller, "~> 1.0"} ``` and create your Telemetry supervisor at `lib/my_app_web/telemetry.ex`: ``` # lib/my_app_web/telemetry.ex defmodule MyAppWeb.Telemetry do use Supervisor import Telemetry.Metrics def start_link(arg) do Supervisor.start_link(__MODULE__, arg, name: __MODULE__) end def init(_arg) do children = [ {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} # Add reporters as children of your supervision tree. # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} ] Supervisor.init(children, strategy: :one_for_one) end def metrics do [ # Phoenix Metrics summary("phoenix.endpoint.stop.duration", unit: {:native, :millisecond} ), summary("phoenix.router_dispatch.stop.duration", tags: [:route], unit: {:native, :millisecond} ), # VM Metrics summary("vm.memory.total", unit: {:byte, :kilobyte}), summary("vm.total_run_queue_lengths.total"), summary("vm.total_run_queue_lengths.cpu"), summary("vm.total_run_queue_lengths.io") ] end defp periodic_measurements do [ # A module, function and arguments to be invoked periodically. # This function must call :telemetry.execute/3 and a metric must be added above. # {MyApp, :count_users, []} ] end end ``` Make sure to replace MyApp by your actual application name. Then add to your main application's supervision tree (usually in `lib/my_app/application.ex`): ``` children = [ MyAppWeb.Telemetry, MyApp.Repo, MyAppWeb.Endpoint, ... ] ``` ## [](telemetry.html#telemetry-events)Telemetry Events Many Elixir libraries (including Phoenix) are already using the [`:telemetry`](../telemetry.html) package as a way to give users more insight into the behavior of their applications, by emitting events at key moments in the application lifecycle. A Telemetry event is made up of the following: - `name` - A string (e.g. `"my_app.worker.stop"`) or a list of atoms that uniquely identifies the event. - `measurements` - A map of atom keys (e.g. `:duration`) and numeric values. - `metadata` - A map of key-value pairs that can be used for tagging metrics. ### [](telemetry.html#a-phoenix-example)A Phoenix Example Here is an example of an event from your endpoint: - `[:phoenix, :endpoint, :stop]` - dispatched by [`Plug.Telemetry`](../plug/1.15.3/Plug.Telemetry.html), one of the default plugs in your endpoint, whenever the response is sent - Measurement: `%{duration: native_time}` - Metadata: `%{conn: Plug.Conn.t}` This means that after each request, [`Plug`](../plug/1.15.3/Plug.html), via `:telemetry`, will emit a "stop" event, with a measurement of how long it took to get the response: ``` :telemetry.execute([:phoenix, :endpoint, :stop], %{duration: duration}, %{conn: conn}) ``` ### [](telemetry.html#phoenix-telemetry-events)Phoenix Telemetry Events A full list of all Phoenix telemetry events can be found in [`Phoenix.Logger`](Phoenix.Logger.html) ## [](telemetry.html#metrics)Metrics > Metrics are aggregations of Telemetry events with a specific name, providing a view of the system's behaviour over time. > > ― [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) The Telemetry.Metrics package provides a common interface for defining metrics. It exposes a set of [five metric type functions](https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html#module-metrics) that are responsible for structuring a given Telemetry event as a particular measurement. The package does not perform any aggregation of the measurements itself. Instead, it provides a reporter with the Telemetry event-as-measurement definition and the reporter uses that definition to perform aggregations and report them. We will discuss reporters in the next section. Let's take a look at some examples. Using [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html), you can define a counter metric, which counts how many HTTP requests were completed: ``` Telemetry.Metrics.counter("phoenix.endpoint.stop.duration") ``` or you could use a distribution metric to see how many requests were completed in particular time buckets: ``` Telemetry.Metrics.distribution("phoenix.endpoint.stop.duration") ``` This ability to introspect HTTP requests is really powerful -- and this is but one of *many* telemetry events emitted by the Phoenix framework! We'll discuss more of these events, as well as specific patterns for extracting valuable data from Phoenix/Plug events in the [Phoenix Metrics](telemetry.html#phoenix-metrics) section later in this guide. > The full list of `:telemetry` events emitted from Phoenix, along with their measurements and metadata, is available in the "Instrumentation" section of the [`Phoenix.Logger`](Phoenix.Logger.html) module documentation. ### [](telemetry.html#an-ecto-example)An Ecto Example Like Phoenix, Ecto ships with built-in Telemetry events. This means that you can gain introspection into your web and database layers using the same tools. Here is an example of a Telemetry event executed by Ecto when an Ecto repository starts: - `[:ecto, :repo, :init]` - dispatched by [`Ecto.Repo`](../ecto/3.10.1/Ecto.Repo.html) - Measurement: `%{system_time: native_time}` - Metadata: `%{repo: Ecto.Repo, opts: Keyword.t()}` This means that whenever the [`Ecto.Repo`](../ecto/3.10.1/Ecto.Repo.html) starts, it will emit an event, via `:telemetry`, with a measurement of the time at start-up. ``` :telemetry.execute([:ecto, :repo, :init], %{system_time: System.system_time()}, %{repo: repo, opts: opts}) ``` Additional Telemetry events are executed by Ecto adapters. One such adapter-specific event is the `[:my_app, :repo, :query]` event. For instance, if you want to graph query execution time, you can use the [`Telemetry.Metrics.summary/2`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html#summary/2) function to instruct your reporter to calculate statistics of the `[:my_app, :repo, :query]` event, like maximum, mean, percentiles etc.: ``` Telemetry.Metrics.summary("my_app.repo.query.query_time", unit: {:native, :millisecond} ) ``` Or you could use the [`Telemetry.Metrics.distribution/2`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html#distribution/2) function to define a histogram for another adapter-specific event: `[:my_app, :repo, :query, :queue_time]`, thus visualizing how long queries spend queued: ``` Telemetry.Metrics.distribution("my_app.repo.query.queue_time", unit: {:native, :millisecond} ) ``` > You can learn more about Ecto Telemetry in the "Telemetry Events" section of the [`Ecto.Repo`](https://hexdocs.pm/ecto/Ecto.Repo.html) module documentation. So far we have seen some of the Telemetry events common to Phoenix applications, along with some examples of their various measurements and metadata. With all of this data just waiting to be consumed, let's talk about reporters. ## [](telemetry.html#reporters)Reporters Reporters subscribe to Telemetry events using the common interface provided by [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html). They then aggregate the measurements (data) into metrics to provide meaningful information about your application. For example, if the following [`Telemetry.Metrics.summary/2`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html#summary/2) call is added to the `metrics/0` function of your Telemetry supervisor: ``` summary("phoenix.endpoint.stop.duration", unit: {:native, :millisecond} ) ``` Then the reporter will attach a listener for the `"phoenix.endpoint.stop.duration"` event and will respond to this event by calculating a summary metric with the given event metadata and reporting on that metric to the appropriate source. ### [](telemetry.html#phoenix-livedashboard)Phoenix.LiveDashboard For developers interested in real-time visualizations for their Telemetry metrics, you may be interested in installing [`LiveDashboard`](../phoenix_live_dashboard.html). LiveDashboard acts as a Telemetry.Metrics reporter to render your data as beautiful, real-time charts on the dashboard. ### [](telemetry.html#telemetry-metrics-consolereporter)Telemetry.Metrics.ConsoleReporter [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) ships with a `ConsoleReporter` that can be used to print events and metrics to the terminal. You can use this reporter to experiment with the metrics discussed in this guide. Uncomment or add the following to this list of children in your Telemetry supervision tree (usually in `lib/my_app_web/telemetry.ex`): ``` {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} ``` > There are numerous reporters available, for services like StatsD, Prometheus, and more. You can find them by searching for "telemetry\_metrics" on [hex.pm](https://hex.pm/packages?search=telemetry_metrics). ## [](telemetry.html#phoenix-metrics)Phoenix Metrics Earlier we looked at the "stop" event emitted by [`Plug.Telemetry`](../plug/1.15.3/Plug.Telemetry.html), and used it to count the number of HTTP requests. In reality, it's only somewhat helpful to be able to see just the total number of requests. What if you wanted to see the number of requests per route, or per route *and* method? Let's take a look at another event emitted during the HTTP request lifecycle, this time from [`Phoenix.Router`](Phoenix.Router.html): - `[:phoenix, :router_dispatch, :stop]` - dispatched by Phoenix.Router after successfully dispatching to a matched route - Measurement: `%{duration: native_time}` - Metadata: `%{conn: Plug.Conn.t, route: binary, plug: module, plug_opts: term, path_params: map, pipe_through: [atom]}` Let's start by grouping these events by route. Add the following (if it does not already exist) to the `metrics/0` function of your Telemetry supervisor (usually in `lib/my_app_web/telemetry.ex`): ``` # lib/my_app_web/telemetry.ex def metrics do [ ...metrics... summary("phoenix.router_dispatch.stop.duration", tags: [:route], unit: {:native, :millisecond} ) ] end ``` Restart your server, and then make requests to a page or two. In your terminal, you should see the ConsoleReporter print logs for the Telemetry events it received as a result of the metrics definitions you provided. The log line for each request contains the specific route for that request. This is due to specifying the `:tags` option for the summary metric, which takes care of our first requirement; we can use `:tags` to group metrics by route. Note that reporters will necessarily handle tags differently depending on the underlying service in use. Looking more closely at the Router "stop" event, you can see that the [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) struct representing the request is present in the metadata, but how do you access the properties in `conn`? Fortunately, [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) provides the following options to help you classify your events: - `:tags` - A list of metadata keys for grouping; - `:tag_values` - A function which transforms the metadata into the desired shape; Note that this function is called for each event, so it's important to keep it fast if the rate of events is high. > Learn about all the available metrics options in the [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) module documentation. Let's find out how to extract more tags from events that include a `conn` in their metadata. ### [](telemetry.html#extracting-tag-values-from-plug-conn)Extracting tag values from Plug.Conn Let's add another metric for the route event, this time to group by route and method: ``` summary("phoenix.router_dispatch.stop.duration", tags: [:method, :route], tag_values: &get_and_put_http_method/1, unit: {:native, :millisecond} ) ``` We've introduced the `:tag_values` option here, because we need to perform a transformation on the event metadata in order to get to the values we need. Add the following private function to your Telemetry module to lift the `:method` value from the [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html) struct: ``` # lib/my_app_web/telemetry.ex defp get_and_put_http_method(%{conn: %{method: method}} = metadata) do Map.put(metadata, :method, method) end ``` Restart your server and make some more requests. You should begin to see logs with tags for both the HTTP method and the route. Note the `:tags` and `:tag_values` options can be applied to all [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) types. ### [](telemetry.html#renaming-value-labels-using-tag-values)Renaming value labels using tag values Sometimes when displaying a metric, the value label may need to be transformed to improve readability. Take for example the following metric that displays the duration of the each LiveView's `mount/3` callback by `connected?` status. ``` summary("phoenix.live_view.mount.stop.duration", unit: {:native, :millisecond}, tags: [:view, :connected?], tag_values: &live_view_metric_tag_values/1 ) ``` The following function lifts `metadata.socket.view` and `metadata.socket.connected?` to be top-level keys on `metadata`, as we did in the previous example. ``` # lib/my_app_web/telemetry.ex defp live_view_metric_tag_values(metadata) do metadata |> Map.put(:view, metadata.socket.view) |> Map.put(:connected?, Phoenix.LiveView.connected?(metadata.socket)) end ``` However, when rendering these metrics in LiveDashboard, the value label is output as `"Elixir.Phoenix.LiveDashboard.MetricsLive true"`. To make the value label easier to read, we can update our private function to generate more user friendly names. We'll run the value of the `:view` through [`inspect/1`](../elixir/Kernel.html#inspect/1) to remove the `Elixir.` prefix and call another private function to convert the `connected?` boolean into human readable text. ``` # lib/my_app_web/telemetry.ex defp live_view_metric_tag_values(metadata) do metadata |> Map.put(:view, inspect(metadata.socket.view)) |> Map.put(:connected?, get_connection_status(Phoenix.LiveView.connected?(metadata.socket))) end defp get_connection_status(true), do: "Connected" defp get_connection_status(false), do: "Disconnected" ``` Now the value label will be rendered like `"Phoenix.LiveDashboard.MetricsLive Connected"`. Hopefully, this gives you some inspiration on how to use the `:tag_values` option. Just remember to keep this function fast since it is called on every event. ## [](telemetry.html#periodic-measurements)Periodic measurements You might want to periodically measure key-value pairs within your application. Fortunately the [`:telemetry_poller`](../telemetry_poller.html) package provides a mechanism for custom measurements, which is useful for retrieving process information or for performing custom measurements periodically. Add the following to the list in your Telemetry supervisor's `periodic_measurements/0` function, which is a private function that returns a list of measurements to take on a specified interval. ``` # lib/my_app_web/telemetry.ex defp periodic_measurements do [ {MyApp, :measure_users, []}, {:process_info, event: [:my_app, :my_server], name: MyApp.MyServer, keys: [:message_queue_len, :memory]} ] end ``` where `MyApp.measure_users/0` could be written like this: ``` # lib/my_app.ex defmodule MyApp do def measure_users do :telemetry.execute([:my_app, :users], %{total: MyApp.users_count()}, %{}) end end ``` Now with measurements in place, you can define the metrics for the events above: ``` # lib/my_app_web/telemetry.ex def metrics do [ ...metrics... # MyApp Metrics last_value("my_app.users.total"), last_value("my_app.my_server.memory", unit: :byte), last_value("my_app.my_server.message_queue_len") summary("my_app.my_server.call.stop.duration"), counter("my_app.my_server.call.exception") ] end ``` > You will implement MyApp.MyServer in the [Custom Events](telemetry.html#custom-events) section. ## [](telemetry.html#libraries-using-telemetry)Libraries using Telemetry Telemetry is quickly becoming the de-facto standard for package instrumentation in Elixir. Here is a list of libraries currently emitting `:telemetry` events. Library authors are actively encouraged to send a PR adding their own (in alphabetical order, please): - [Absinthe](../absinthe.html) - [Events](../absinthe/telemetry.html) - [Ash Framework](../ash.html) - [Events](../ash/monitoring.html) - [Broadway](../broadway.html) - [Events](../broadway/Broadway.html#module-telemetry) - [Ecto](../ecto.html) - [Events](https://hexdocs.pm/ecto/Ecto.Repo.html#module-telemetry-events) - [Oban](../oban.html) - [Events](../oban/Oban.Telemetry.html) - [Phoenix](../phoenix.html) - [Events](Phoenix.Logger.html#module-instrumentation) - [Plug](../plug.html) - [Events](https://hexdocs.pm/plug/Plug.Telemetry.html) - [Tesla](../tesla.html) - [Events](../tesla/Tesla.Middleware.Telemetry.html) ## [](telemetry.html#custom-events)Custom Events If you need custom metrics and instrumentation in your application, you can utilize the `:telemetry` package ([https://hexdocs.pm/telemetry](../telemetry.html)) just like your favorite frameworks and libraries. Here is an example of a simple GenServer that emits telemetry events. Create this file in your app at `lib/my_app/my_server.ex`: ``` # lib/my_app/my_server.ex defmodule MyApp.MyServer do @moduledoc """ An example GenServer that runs arbitrary functions and emits telemetry events when called. """ use GenServer # A common prefix for :telemetry events @prefix [:my_app, :my_server, :call] def start_link(fun) do GenServer.start_link(__MODULE__, fun, name: __MODULE__) end @doc """ Runs the function contained within this server. ## Events The following events may be emitted: * `[:my_app, :my_server, :call, :start]` - Dispatched immediately before invoking the function. This event is always emitted. * Measurement: `%{system_time: system_time}` * Metadata: `%{}` * `[:my_app, :my_server, :call, :stop]` - Dispatched immediately after successfully invoking the function. * Measurement: `%{duration: native_time}` * Metadata: `%{}` * `[:my_app, :my_server, :call, :exception]` - Dispatched immediately after invoking the function, in the event the function throws or raises. * Measurement: `%{duration: native_time}` * Metadata: `%{kind: kind, reason: reason, stacktrace: stacktrace}` """ def call!, do: GenServer.call(__MODULE__, :called) @impl true def init(fun) when is_function(fun, 0), do: {:ok, fun} @impl true def handle_call(:called, _from, fun) do # Wrap the function invocation in a "span" result = telemetry_span(fun) {:reply, result, fun} end # Emits telemetry events related to invoking the function defp telemetry_span(fun) do start_time = emit_start() try do fun.() catch kind, reason -> stacktrace = System.stacktrace() duration = System.monotonic_time() - start_time emit_exception(duration, kind, reason, stacktrace) :erlang.raise(kind, reason, stacktrace) else result -> duration = System.monotonic_time() - start_time emit_stop(duration) result end end defp emit_start do start_time_mono = System.monotonic_time() :telemetry.execute( @prefix ++ [:start], %{system_time: System.system_time()}, %{} ) start_time_mono end defp emit_stop(duration) do :telemetry.execute( @prefix ++ [:stop], %{duration: duration}, %{} ) end defp emit_exception(duration, kind, reason, stacktrace) do :telemetry.execute( @prefix ++ [:exception], %{duration: duration}, %{ kind: kind, reason: reason, stacktrace: stacktrace } ) end end ``` and add it to your application's supervisor tree (usually in `lib/my_app/application.ex`), giving it a function to invoke when called: ``` # lib/my_app/application.ex children = [ # Start a server that greets the world {MyApp.MyServer, fn -> "Hello, world!" end}, ] ``` Now start an IEx session and call the server: ``` iex> MyApp.MyServer.call! ``` and you should see something like the following output: ``` [Telemetry.Metrics.ConsoleReporter] Got new event! Event name: my_app.my_server.call.stop All measurements: %{duration: 4000} All metadata: %{} Metric measurement: #Function<2.111777250/1 in Telemetry.Metrics.maybe_convert_measurement/2> (summary) With value: 0.004 millisecond Tag values: %{} "Hello, world!" ``` [← Previous Page Mix tasks](mix_tasks.html) [Next Page → Asset Management](asset_management.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/telemetry.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/testing.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/testing/testing.md#L1 "View Source") Introduction to Testing > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). Testing has become integral to the software development process, and the ability to easily write meaningful tests is an indispensable feature for any modern web framework. Phoenix takes this seriously, providing support files to make all the major components of the framework easy to test. It also generates test modules with real-world examples alongside any generated modules to help get us going. Elixir ships with a built-in testing framework called [ExUnit](../ex_unit.html). ExUnit strives to be clear and explicit, keeping magic to a minimum. Phoenix uses ExUnit for all of its testing, and we will use it here as well. ## [](testing.html#running-tests)Running tests When Phoenix generates a web application for us, it also includes tests. To run them, simply type [`mix test`](../mix/Mix.Tasks.Test.html): ``` $ mix test .... Finished in 0.09 seconds 5 tests, 0 failures Randomized with seed 652656 ``` We already have five tests! In fact, we already have a directory structure completely set up for testing, including a test helper and support files. ``` test ├── hello_web │   └── controllers │      ├── error_html_test.exs │      ├── error_json_test.exs │      └── page_controller_test.exs ├── support │   ├── conn_case.ex │   └── data_case.ex └── test_helper.exs ``` The test cases we get for free include those from `test/hello_web/controllers/`. They are testing our controllers and views. If you haven't read the guides for controllers and views, now is a good time. ## [](testing.html#understanding-test-modules)Understanding test modules We are going to use the next sections to get acquainted with Phoenix testing structure. We will start with the three test files generated by Phoenix. The first test file we'll look at is `test/hello_web/controllers/page_controller_test.exs`. ``` defmodule HelloWeb.PageControllerTest do use HelloWeb.ConnCase test "GET /", %{conn: conn} do conn = get(conn, ~p"/") assert html_response(conn, 200) =~ "Peace of mind from prototype to production" end end ``` There are a couple of interesting things happening here. Our test files simply define modules. At the top of each module, you will find a line such as: ``` use HelloWeb.ConnCase ``` If you were to write an Elixir library, outside of Phoenix, instead of `use HelloWeb.ConnCase` you would write `use ExUnit.Case`. However, Phoenix already ships with a bunch of functionality for testing controllers and `HelloWeb.ConnCase` builds on top of [`ExUnit.Case`](../ex_unit/ExUnit.Case.html) to bring these functionalities in. We will explore the `HelloWeb.ConnCase` module soon. Then we define each test using the `test/3` macro. The `test/3` macro receives three arguments: the test name, the testing context that we are pattern matching on, and the contents of the test. In this test, we access the root page of our application by a "GET" HTTP request on the path "/" with the `get/2` macro. Then we **assert** that the rendered page contains the string "Peace of mind from prototype to production". When writing tests in Elixir, we use assertions to check that something is true. In our case, `assert html_response(conn, 200) =~ "Peace of mind from prototype to production"` is doing a couple things: - It asserts that `conn` has rendered a response - It asserts that the response has the 200 status code (which means OK in HTTP parlance) - It asserts that the type of the response is HTML - It asserts that the result of `html_response(conn, 200)`, which is an HTML response, has the string "Peace of mind from prototype to production" in it However, from where does the `conn` we use on `get` and `html_response` come from? To answer this question, let's take a look at `HelloWeb.ConnCase`. ## [](testing.html#the-conncase)The ConnCase If you open up `test/support/conn_case.ex`, you will find this (with comments removed): ``` defmodule HelloWeb.ConnCase do use ExUnit.CaseTemplate using do quote do # The default endpoint for testing @endpoint HelloWeb.Endpoint use HelloWeb, :verified_routes # Import conveniences for testing with connections import Plug.Conn import Phoenix.ConnTest import HelloWeb.ConnCase end end setup tags do Hello.DataCase.setup_sandbox(tags) {:ok, conn: Phoenix.ConnTest.build_conn()} end end ``` There is a lot to unpack here. The second line says this is a case template. This is a ExUnit feature that allows developers to replace the built-in `use ExUnit.Case` by their own case. This line is pretty much what allows us to write `use HelloWeb.ConnCase` at the top of our controller tests. Now that we have made this module a case template, we can define callbacks that are invoked on certain occasions. The `using` callback defines code to be injected on every module that calls `use HelloWeb.ConnCase`. In this case, it starts by setting the `@endpoint` module attribute with the name of our endpoint. Next, it wires up `:verified_routes` to allow us to use `~p` based paths in our test just like we do in the rest of our application to easily generate paths and URLs in our tests. Finally, we import [`Plug.Conn`](https://hexdocs.pm/plug/Plug.Conn.html), so all of the connection helpers available in controllers are also available in tests, and then imports [`Phoenix.ConnTest`](Phoenix.ConnTest.html). You can consult these modules to learn all functionality available. Then our case template defines a `setup` block. The `setup` block will be called before test. Most of the setup block is on setting up the SQL Sandbox, which we will talk about later. In the last line of the `setup` block, we will find this: ``` {:ok, conn: Phoenix.ConnTest.build_conn()} ``` The last line of `setup` can return test metadata that will be available in each test. The metadata we are passing forward here is a newly built [`Plug.Conn`](../plug/1.15.3/Plug.Conn.html). In our test, we extract the connection out of this metadata at the very beginning of our test: ``` test "GET /", %{conn: conn} do ``` And that's where the connection comes from! At first, the testing structure does come with a bit of indirection, but this indirection pays off as our test suite grows, since it allows us to cut down the amount of boilerplate. ## [](testing.html#view-tests)View tests The other test files in our application are responsible for testing our views. The error view test case, `test/hello_web/controllers/error_html_test.exs`, illustrates a few interesting things of its own. ``` defmodule HelloWeb.ErrorHTMLTest do use HelloWeb.ConnCase, async: true # Bring render_to_string/4 for testing custom views import Phoenix.Template test "renders 404.html" do assert render_to_string(HelloWeb.ErrorHTML, "404", "html", []) == "Not Found" end test "renders 500.html" do assert render_to_string(HelloWeb.ErrorHTML, "500", "html", []) == "Internal Server Error" end end ``` `HelloWeb.ErrorHTMLTest` sets `async: true` which means that this test case will be run in parallel with other test cases. While individual tests within the case still run serially, this can greatly increase overall test speeds. It also imports [`Phoenix.Template`](../phoenix_template/1.0.4/Phoenix.Template.html) in order to use the `render_to_string/4` function. With that, all the assertions can be simple string equality tests. ## [](testing.html#running-tests-per-directory-file)Running tests per directory/file Now that we have an idea what our tests are doing, let's look at different ways to run them. As we saw near the beginning of this guide, we can run our entire suite of tests with [`mix test`](../mix/Mix.Tasks.Test.html). ``` $ mix test .... Finished in 0.2 seconds 5 tests, 0 failures Randomized with seed 540755 ``` If we would like to run all the tests in a given directory, `test/hello_web/controllers` for instance, we can pass the path to that directory to [`mix test`](../mix/Mix.Tasks.Test.html). ``` $ mix test test/hello_web/controllers/ . Finished in 0.2 seconds 5 tests, 0 failures Randomized with seed 652376 ``` In order to run all the tests in a specific file, we can pass the path to that file into [`mix test`](../mix/Mix.Tasks.Test.html). ``` $ mix test test/hello_web/controllers/error_html_test.exs ... Finished in 0.2 seconds 2 tests, 0 failures Randomized with seed 220535 ``` And we can run a single test in a file by appending a colon and a line number to the filename. Let's say we only wanted to run the test for the way `HelloWeb.ErrorHTML` renders `500.html`. The test begins on line 11 of the file, so this is how we would do it. ``` $ mix test test/hello_web/controllers/error_html_test.exs:11 Including tags: [line: "11"] Excluding tags: [:test] . Finished in 0.1 seconds 2 tests, 0 failures, 1 excluded Randomized with seed 288117 ``` We chose to run this specifying the first line of the test, but actually, any line of that test will do. These line numbers would all work - `:11`, `:12`, or `:13`. ## [](testing.html#running-tests-using-tags)Running tests using tags ExUnit allows us to tag our tests individually or for the whole module. We can then choose to run only the tests with a specific tag, or we can exclude tests with that tag and run everything else. Let's experiment with how this works. First, we'll add a `@moduletag` to `test/hello_web/controllers/error_html_test.exs`. ``` defmodule HelloWeb.ErrorHTMLTest do use HelloWeb.ConnCase, async: true @moduletag :error_view_case ... end ``` If we use only an atom for our module tag, ExUnit assumes that it has a value of `true`. We could also specify a different value if we wanted. ``` defmodule HelloWeb.ErrorHTMLTest do use HelloWeb.ConnCase, async: true @moduletag error_view_case: "some_interesting_value" ... end ``` For now, let's leave it as a simple atom `@moduletag :error_view_case`. We can run only the tests from the error view case by passing `--only error_view_case` into [`mix test`](../mix/Mix.Tasks.Test.html). ``` $ mix test --only error_view_case Including tags: [:error_view_case] Excluding tags: [:test] ... Finished in 0.1 seconds 5 tests, 0 failures, 3 excluded Randomized with seed 125659 ``` > Note: ExUnit tells us exactly which tags it is including and excluding for each test run. If we look back to the previous section on running tests, we'll see that line numbers specified for individual tests are actually treated as tags. ``` $ mix test test/hello_web/controllers/error_html_test.exs:11 Including tags: [line: "11"] Excluding tags: [:test] . Finished in 0.2 seconds 2 tests, 0 failures, 1 excluded Randomized with seed 364723 ``` Specifying a value of `true` for `error_view_case` yields the same results. ``` $ mix test --only error_view_case:true Including tags: [error_view_case: "true"] Excluding tags: [:test] ... Finished in 0.1 seconds 5 tests, 0 failures, 3 excluded Randomized with seed 833356 ``` Specifying `false` as the value for `error_view_case`, however, will not run any tests because no tags in our system match `error_view_case: false`. ``` $ mix test --only error_view_case:false Including tags: [error_view_case: "false"] Excluding tags: [:test] Finished in 0.1 seconds 5 tests, 0 failures, 5 excluded Randomized with seed 622422 The --only option was given to "mix test" but no test executed ``` We can use the `--exclude` flag in a similar way. This will run all of the tests except those in the error view case. ``` $ mix test --exclude error_view_case Excluding tags: [:error_view_case] . Finished in 0.2 seconds 5 tests, 0 failures, 2 excluded Randomized with seed 682868 ``` Specifying values for a tag works the same way for `--exclude` as it does for `--only`. We can tag individual tests as well as full test cases. Let's tag a few tests in the error view case to see how this works. ``` defmodule HelloWeb.ErrorHTMLTest do use HelloWeb.ConnCase, async: true @moduletag :error_view_case # Bring render/4 and render_to_string/4 for testing custom views import Phoenix.Template @tag individual_test: "yup" test "renders 404.html" do assert render_to_string(HelloWeb.ErrorView, "404", "html", []) == "Not Found" end @tag individual_test: "nope" test "renders 500.html" do assert render_to_string(HelloWeb.ErrorView, "500", "html", []) == "Internal Server Error" end end ``` If we would like to run only tests tagged as `individual_test`, regardless of their value, this will work. ``` $ mix test --only individual_test Including tags: [:individual_test] Excluding tags: [:test] .. Finished in 0.1 seconds 5 tests, 0 failures, 3 excluded Randomized with seed 813729 ``` We can also specify a value and run only tests with that. ``` $ mix test --only individual_test:yup Including tags: [individual_test: "yup"] Excluding tags: [:test] . Finished in 0.1 seconds 5 tests, 0 failures, 4 excluded Randomized with seed 770938 ``` Similarly, we can run all tests except for those tagged with a given value. ``` $ mix test --exclude individual_test:nope Excluding tags: [individual_test: "nope"] ... Finished in 0.2 seconds 5 tests, 0 failures, 1 excluded Randomized with seed 539324 ``` We can be more specific and exclude all the tests from the error view case except the one tagged with `individual_test` that has the value "yup". ``` $ mix test --exclude error_view_case --include individual_test:yup Including tags: [individual_test: "yup"] Excluding tags: [:error_view_case] .. Finished in 0.2 seconds 5 tests, 0 failures, 1 excluded Randomized with seed 61472 ``` Finally, we can configure ExUnit to exclude tags by default. The default ExUnit configuration is done in the `test/test_helper.exs` file: ``` ExUnit.start(exclude: [error_view_case: true]) Ecto.Adapters.SQL.Sandbox.mode(Hello.Repo, :manual) ``` Now when we run [`mix test`](../mix/Mix.Tasks.Test.html), it only runs the specs from our `page_controller_test.exs` and `error_json_test.exs`. ``` $ mix test Excluding tags: [error_view_case: true] . Finished in 0.2 seconds 5 tests, 0 failures, 2 excluded Randomized with seed 186055 ``` We can override this behavior with the `--include` flag, telling [`mix test`](../mix/Mix.Tasks.Test.html) to include tests tagged with `error_view_case`. ``` $ mix test --include error_view_case Including tags: [:error_view_case] Excluding tags: [error_view_case: true] .... Finished in 0.2 seconds 5 tests, 0 failures Randomized with seed 748424 ``` This technique can be very useful to control very long running tests, which you may only want to run in CI or in specific scenarios. ## [](testing.html#randomization)Randomization Running tests in random order is a good way to ensure that our tests are truly isolated. If we notice that we get sporadic failures for a given test, it may be because a previous test changes the state of the system in ways that aren't cleaned up afterward, thereby affecting the tests which follow. Those failures might only present themselves if the tests are run in a specific order. ExUnit will randomize the order tests run in by default, using an integer to seed the randomization. If we notice that a specific random seed triggers our intermittent failure, we can re-run the tests with that same seed to reliably recreate that test sequence in order to help us figure out what the problem is. ``` $ mix test --seed 401472 .... Finished in 0.2 seconds 5 tests, 0 failures Randomized with seed 401472 ``` ## [](testing.html#concurrency-and-partitioning)Concurrency and partitioning As we have seen, ExUnit allows developers to run tests concurrently. This allows developers to use all of the power in their machine to run their test suites as fast as possible. Couple this with Phoenix performance, most test suites compile and run in a fraction of the time compared to other frameworks. While developers usually have powerful machines available to them during development, this may not always be the case in your Continuous Integration servers. For this reason, ExUnit also supports out of the box test partitioning in test environments. If you open up your `config/test.exs`, you will find the database name set to: ``` database: "hello_test#{System.get_env("MIX_TEST_PARTITION")}", ``` By default, the `MIX_TEST_PARTITION` environment variable has no value, and therefore it has no effect. But in your CI server, you can, for example, split your test suite across machines by using four distinct commands: ``` $ MIX_TEST_PARTITION=1 mix test --partitions 4 $ MIX_TEST_PARTITION=2 mix test --partitions 4 $ MIX_TEST_PARTITION=3 mix test --partitions 4 $ MIX_TEST_PARTITION=4 mix test --partitions 4 ``` That's all you need to do and ExUnit and Phoenix will take care of all rest, including setting up the database for each distinct partition with a distinct name. ## [](testing.html#going-further)Going further While ExUnit is a simple test framework, it provides a really flexible and robust test runner through the [`mix test`](../mix/Mix.Tasks.Test.html) command. We recommend you to run [`mix help test`](../mix/Mix.Tasks.Test.html) or [read the docs online](../mix/Mix.Tasks.Test.html) We've seen what Phoenix gives us with a newly generated app. Furthermore, whenever you generate a new resource, Phoenix will generate all appropriate tests for that resource too. For example, you can create a complete scaffold with schema, context, controllers, and views by running the following command at the root of your application: ``` $ mix phx.gen.html Blog Post posts title body:text * creating lib/hello_web/controllers/post_controller.ex * creating lib/hello_web/controllers/post_html/edit.html.heex * creating lib/hello_web/controllers/post_html/index.html.heex * creating lib/hello_web/controllers/post_html/new.html.heex * creating lib/hello_web/controllers/post_html/show.html.heex * creating lib/hello_web/controllers/post_html/post_form.html.heex * creating lib/hello_web/controllers/post_html.ex * creating test/hello_web/controllers/post_controller_test.exs * creating lib/hello/blog/post.ex * creating priv/repo/migrations/20211001233016_create_posts.exs * creating lib/hello/blog.ex * injecting lib/hello/blog.ex * creating test/hello/blog_test.exs * injecting test/hello/blog_test.exs * creating test/support/fixtures/blog_fixtures.ex * injecting test/support/fixtures/blog_fixtures.ex Add the resource to your browser scope in lib/demo_web/router.ex: resources "/posts", PostController Remember to update your repository by running migrations: $ mix ecto.migrate ``` Now let's follow the directions and add the new resources route to our `lib/hello_web/router.ex` file and run the migrations. When we run [`mix test`](../mix/Mix.Tasks.Test.html) again, we see that we now have twenty-one tests! ``` $ mix test ................ Finished in 0.1 seconds 21 tests, 0 failures Randomized with seed 537537 ``` At this point, we are at a great place to transition to the rest of the testing guides, in which we'll examine these tests in much more detail, and add some of our own. [← Previous Page Presence](presence.html) [Next Page → Testing Contexts](testing_contexts.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/testing/testing.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/testing_channels.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/testing/testing_channels.md#L1 "View Source") Testing Channels > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html). > **Requirement**: This guide expects that you have gone through the [Channels guide](channels.html). In the Channels guide, we saw that a "Channel" is a layered system with different components. Given this, there would be cases when writing unit tests for our Channel functions may not be enough. We may want to verify that its different moving parts are working together as we expect. This integration testing would assure us that we correctly defined our channel route, the channel module, and its callbacks; and that the lower-level layers such as the PubSub and Transport are configured correctly and are working as intended. ## [](testing_channels.html#generating-channels)Generating channels As we progress through this guide, it would help to have a concrete example we could work off of. Phoenix comes with a Mix task for generating a basic channel and tests. These generated files serve as a good reference for writing channels and their corresponding tests. Let's go ahead and generate our Channel: ``` $ mix phx.gen.channel Room * creating lib/hello_web/channels/room_channel.ex * creating test/hello_web/channels/room_channel_test.exs * creating test/support/channel_case.ex The default socket handler - HelloWeb.UserSocket - was not found. Do you want to create it? [Yn] * creating lib/hello_web/channels/user_socket.ex * creating assets/js/user_socket.js Add the socket handler to your `lib/hello_web/endpoint.ex`, for example: socket "/socket", HelloWeb.UserSocket, websocket: true, longpoll: false For the front-end integration, you need to import the `user_socket.js` in your `assets/js/app.js` file: import "./user_socket.js" ``` This creates a channel, its test and instructs us to add a channel route in `lib/hello_web/channels/user_socket.ex`. It is important to add the channel route or our channel won't function at all! ## [](testing_channels.html#the-channelcase)The ChannelCase Open up `test/hello_web/channels/room_channel_test.exs` and you will find this: ``` defmodule HelloWeb.RoomChannelTest do use HelloWeb.ChannelCase ``` Similar to `ConnCase` and `DataCase`, we now have a `ChannelCase`. All three of them have been generated for us when we started our Phoenix application. Let's take a look at it. Open up `test/support/channel_case.ex`: ``` defmodule HelloWeb.ChannelCase do use ExUnit.CaseTemplate using do quote do # Import conveniences for testing with channels import Phoenix.ChannelTest import HelloWeb.ChannelCase # The default endpoint for testing @endpoint HelloWeb.Endpoint end end setup _tags do Hello.DataCase.setup_sandbox(tags) :ok end end ``` It is very straight-forward. It sets up a case template that imports all of [`Phoenix.ChannelTest`](Phoenix.ChannelTest.html) on use. In the `setup` block, it starts the SQL Sandbox, which we discussed in the [Testing contexts guide](testing_contexts.html). ## [](testing_channels.html#subscribe-and-joining)Subscribe and joining Now that we know that Phoenix provides with a custom Test Case just for channels and what it provides, we can move on to understanding the rest of `test/hello_web/channels/room_channel_test.exs`. First off, is the setup block: ``` setup do {:ok, _, socket} = HelloWeb.UserSocket |> socket("user_id", %{some: :assign}) |> subscribe_and_join(HelloWeb.RoomChannel, "room:lobby") %{socket: socket} end ``` The `setup` block sets up a [`Phoenix.Socket`](Phoenix.Socket.html) based on the `UserSocket` module, which you can find at `lib/hello_web/channels/user_socket.ex`. Then it says we want to subscribe and join the `RoomChannel`, accessible as `"room:lobby"` in the `UserSocket`. At the end of the test, we return the `%{socket: socket}` as metadata, so we can reuse it on every test. In a nutshell, `subscribe_and_join/3` emulates the client joining a channel and subscribes the test process to the given topic. This is a necessary step since clients need to join a channel before they can send and receive events on that channel. ## [](testing_channels.html#testing-a-synchronous-reply)Testing a synchronous reply The first test block in our generated channel test looks like: ``` test "ping replies with status ok", %{socket: socket} do ref = push(socket, "ping", %{"hello" => "there"}) assert_reply ref, :ok, %{"hello" => "there"} end ``` This tests the following code in our `HelloWeb.RoomChannel`: ``` # Channels can be used in a request/response fashion # by sending replies to requests from the client def handle_in("ping", payload, socket) do {:reply, {:ok, payload}, socket} end ``` As is stated in the comment above, we see that a `reply` is synchronous since it mimics the request/response pattern we are familiar with in HTTP. This synchronous reply is best used when we only want to send an event back to the client when we are done processing the message on the server. For example, when we save something to the database and then send a message to the client only once that's done. In the `test "ping replies with status ok", %{socket: socket} do` line, we see that we have the map `%{socket: socket}`. This gives us access to the `socket` in the setup block. We emulate the client pushing a message to the channel with `push/3`. In the line `ref = push(socket, "ping", %{"hello" => "there"})`, we push the event `"ping"` with the payload `%{"hello" => "there"}` to the channel. This triggers the `handle_in/3` callback we have for the `"ping"` event in our channel. Note that we store the `ref` since we need that on the next line for asserting the reply. With `assert_reply ref, :ok, %{"hello" => "there"}`, we assert that the server sends a synchronous reply `:ok, %{"hello" => "there"}`. This is how we check that the `handle_in/3` callback for the `"ping"` was triggered. ### [](testing_channels.html#testing-a-broadcast)Testing a Broadcast It is common to receive messages from the client and broadcast to everyone subscribed to a current topic. This common pattern is simple to express in Phoenix and is one of the generated `handle_in/3` callbacks in our `HelloWeb.RoomChannel`. ``` def handle_in("shout", payload, socket) do broadcast(socket, "shout", payload) {:noreply, socket} end ``` Its corresponding test looks like: ``` test "shout broadcasts to room:lobby", %{socket: socket} do push(socket, "shout", %{"hello" => "all"}) assert_broadcast "shout", %{"hello" => "all"} end ``` We notice that we access the same `socket` that is from the setup block. How handy! We also do the same `push/3` as we did in the synchronous reply test. So we `push` the `"shout"` event with the payload `%{"hello" => "all"}`. Since the `handle_in/3` callback for the `"shout"` event just broadcasts the same event and payload, all subscribers in the `"room:lobby"` should receive the message. To check that, we do `assert_broadcast "shout", %{"hello" => "all"}`. **NOTE:** `assert_broadcast/3` tests that the message was broadcast in the PubSub system. For testing if a client receives a message, use `assert_push/3`. ### [](testing_channels.html#testing-an-asynchronous-push-from-the-server)Testing an asynchronous push from the server The last test in our `HelloWeb.RoomChannelTest` verifies that broadcasts from the server are pushed to the client. Unlike the previous tests discussed, we are indirectly testing that the channel's `handle_out/3` callback is triggered. By default, `handle_out/3` is implemented for us and simply pushes the message on to the client. Since the `handle_out/3` event is only triggered when we call `broadcast/3` from our channel, we will need to emulate that in our test. We do that by calling `broadcast_from` or `broadcast_from!`. Both serve the same purpose with the only difference of `broadcast_from!` raising an error when broadcast fails. The line `broadcast_from!(socket, "broadcast", %{"some" => "data"})` will trigger the `handle_out/3` callback which pushes the same event and payload back to the client. To test this, we do `assert_push "broadcast", %{"some" => "data"}`. That's it. Now you are ready to develop and fully test real-time applications. To learn more about other functionality provided when testing channels, check out the documentation for [`Phoenix.ChannelTest`](Phoenix.ChannelTest.html). [← Previous Page Testing Controllers](testing_controllers.html) [Next Page → Introduction to Deployment](deployment.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/testing/testing_channels.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/testing_contexts.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/testing/testing_contexts.md#L1 "View Source") Testing Contexts > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html). > **Requirement**: This guide expects that you have gone through the [Contexts guide](contexts.html). At the end of the Introduction to Testing guide, we generated an HTML resource for posts using the following command: ``` $ mix phx.gen.html Blog Post posts title body:text ``` This gave us a number of modules for free, including a Blog context and a Post schema, alongside their respective test files. As we have learned in the Context guide, the Blog context is simply a module with functions to a particular area of our business domain, while Post schema maps to a particular table in our database. In this guide, we are going to explore the tests generated for our contexts and schemas. Before we do anything else, let's run [`mix test`](../mix/Mix.Tasks.Test.html) to make sure our test suite runs cleanly. ``` $ mix test ................ Finished in 0.6 seconds 21 tests, 0 failures Randomized with seed 638414 ``` Great. We've got twenty-one tests and they are all passing! ## [](testing_contexts.html#testing-posts)Testing posts If you open up `test/hello/blog_test.exs`, you will see a file with the following: ``` defmodule Hello.BlogTest do use Hello.DataCase alias Hello.Blog describe "posts" do alias Hello.Blog.Post import Hello.BlogFixtures @invalid_attrs %{body: nil, title: nil} test "list_posts/0 returns all posts" do post = post_fixture() assert Blog.list_posts() == [post] end ... ``` As the top of the file we import `Hello.DataCase`, which as we will see soon, it is similar to `HelloWeb.ConnCase`. While `HelloWeb.ConnCase` sets up helpers for working with connections, which is useful when testing controllers and views, `Hello.DataCase` provides functionality for working with contexts and schemas. Next, we define an alias, so we can refer to `Hello.Blog` simply as `Blog`. Then we start a `describe "posts"` block. A `describe` block is a feature in ExUnit that allows us to group similar tests. The reason why we have grouped all post related tests together is because contexts in Phoenix are capable of grouping multiple schemas together. For example, if we ran this command: ``` $ mix phx.gen.html Blog Comment comments post_id:references:posts body:text ``` We will get a bunch of new functions in the `Hello.Blog` context, plus a whole new `describe "comments"` block in our test file. The tests defined for our context are very straight-forward. They call the functions in our context and assert on their results. As you can see, some of those tests even create entries in the database: ``` test "create_post/1 with valid data creates a post" do valid_attrs = %{body: "some body", title: "some title"} assert {:ok, %Post{} = post} = Blog.create_post(valid_attrs) assert post.body == "some body" assert post.title == "some title" end ``` At this point, you may wonder: how can Phoenix make sure the data created in one of the tests do not affect other tests? We are glad you asked. To answer this question, let's talk about the `DataCase`. ## [](testing_contexts.html#the-datacase)The DataCase If you open up `test/support/data_case.ex`, you will find the following: ``` defmodule Hello.DataCase do use ExUnit.CaseTemplate using do quote do alias Hello.Repo import Ecto import Ecto.Changeset import Ecto.Query import Hello.DataCase end end setup tags do Hello.DataCase.setup_sandbox(tags) :ok end def setup_sandbox(tags) do pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Hello.Repo, shared: not tags[:async]) on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) end def errors_on(changeset) do ... end end ``` `Hello.DataCase` is another [`ExUnit.CaseTemplate`](../ex_unit/ExUnit.CaseTemplate.html). In the `using` block, we can see all of the aliases and imports `DataCase` brings into our tests. The `setup` chunk for `DataCase` is very similar to the one from `ConnCase`. As we can see, most of the `setup` block revolves around setting up a SQL Sandbox. The SQL Sandbox is precisely what allows our tests to write to the database without affecting any of the other tests. In a nutshell, at the beginning of every test, we start a transaction in the database. When the test is over, we automatically rollback the transaction, effectively erasing all of the data created in the test. Furthermore, the SQL Sandbox allows multiple tests to run concurrently, even if they talk to the database. This feature is provided for PostgreSQL databases and it can be used to further speed up your contexts and controllers tests by adding a `async: true` flag when using them: ``` use Hello.DataCase, async: true ``` There are some considerations you need to have in mind when running asynchronous tests with the sandbox, so please refer to the [`Ecto.Adapters.SQL.Sandbox`](https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html) for more information. Finally at the end of the of the `DataCase` module we can find a function named `errors_on` with some examples of how to use it. This function is used for testing any validation we may want to add to our schemas. Let's give it a try by adding our own validations and then testing them. ## [](testing_contexts.html#testing-schemas)Testing schemas When we generate our HTML Post resource, Phoenix generated a Blog context and a Post schema. It generated a test file for the context, but no test file for the schema. However, this doesn't mean we don't need to test the schema, it just means we did not have to test the schema so far. You may be wondering then: when do we test the context directly and when do we test the schema directly? The answer to this question is the same answer to the question of when do we add code to a context and when do we add it to the schema? The general guideline is to keep all side-effect free code in the schema. In other words, if you are simply working with data structures, schemas and changesets, put it in the schema. The context will typically have the code that creates and updates schemas and then write them to a database or an API. We'll be adding additional validations to the schema module, so that's a great opportunity to write some schema specific tests. Open up `lib/hello/blog/post.ex` and add the following validation to `def changeset`: ``` def changeset(post, attrs) do post |> cast(attrs, [:title, :body]) |> validate_required([:title, :body]) |> validate_length(:title, min: 2) end ``` The new validation says the title needs to have at least 2 characters. Let's write a test for this. Create a new file at `test/hello/blog/post_test.exs` with this: ``` defmodule Hello.Blog.PostTest do use Hello.DataCase, async: true alias Hello.Blog.Post test "title must be at least two characters long" do changeset = Post.changeset(%Post{}, %{title: "I"}) assert %{title: ["should be at least 2 character(s)"]} = errors_on(changeset) end end ``` And that's it. As our business domain grows, we have well-defined places to test our contexts and schemas. [← Previous Page Introduction to Testing](testing.html) [Next Page → Testing Controllers](testing_controllers.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/testing/testing_contexts.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/testing_controllers.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/testing/testing_controllers.md#L1 "View Source") Testing Controllers > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html). > **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html). At the end of the Introduction to Testing guide, we generated an HTML resource for posts using the following command: ``` $ mix phx.gen.html Blog Post posts title body:text ``` This gave us a number of modules for free, including a PostController and the associated tests. We are going to explore those tests to learn more about testing controllers in general. At the end of the guide, we will generate a JSON resource, and explore how our API tests look like. ## [](testing_controllers.html#html-controller-tests)HTML controller tests If you open up `test/hello_web/controllers/post_controller_test.exs`, you will find the following: ``` defmodule HelloWeb.PostControllerTest do use HelloWeb.ConnCase import Hello.BlogFixtures @create_attrs %{body: "some body", title: "some title"} @update_attrs %{body: "some updated body", title: "some updated title"} @invalid_attrs %{body: nil, title: nil} describe "index" do test "lists all posts", %{conn: conn} do conn = get(conn, ~p"/posts") assert html_response(conn, 200) =~ "Listing Posts" end end ... ``` Similar to the `PageControllerTest` that ships with our application, this controller tests uses `use HelloWeb.ConnCase` to setup the testing structure. Then, as usual, it defines some aliases, some module attributes to use throughout testing, and then it starts a series of `describe` blocks, each of them to test a different controller action. ### [](testing_controllers.html#the-index-action)The index action The first describe block is for the `index` action. The action itself is implemented like this in `lib/hello_web/controllers/post_controller.ex`: ``` def index(conn, _params) do posts = Blog.list_posts() render(conn, :index, posts: posts) end ``` It gets all posts and renders the "index.html" template. The template can be found in `lib/hello_web/templates/page/index.html.heex`. The test looks like this: ``` describe "index" do test "lists all posts", %{conn: conn} do conn = get(conn, ~p"/posts") assert html_response(conn, 200) =~ "Listing Posts" end end ``` The test for the `index` page is quite straight-forward. It uses the `get/2` helper to make a request to the `"/posts"` page, which is verified against our router in the test thanks to `~p`, then we assert we got a successful HTML response and match on its contents. ### [](testing_controllers.html#the-create-action)The create action The next test we will look at is the one for the `create` action. The `create` action implementation is this: ``` def create(conn, %{"post" => post_params}) do case Blog.create_post(post_params) do {:ok, post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: ~p"/posts/#{post}") {:error, %Ecto.Changeset{} = changeset} -> render(conn, :new, changeset: changeset) end end ``` Since there are two possible outcomes for the `create`, we will have at least two tests: ``` describe "create post" do test "redirects to show when data is valid", %{conn: conn} do conn = post(conn, ~p"/posts", post: @create_attrs) assert %{id: id} = redirected_params(conn) assert redirected_to(conn) == ~p"/posts/#{id}" conn = get(conn, ~p"/posts/#{id}") assert html_response(conn, 200) =~ "Post #{id}" end test "renders errors when data is invalid", %{conn: conn} do conn = post(conn, ~p"/posts", post: @invalid_attrs) assert html_response(conn, 200) =~ "New Post" end end ``` The first test starts with a `post/2` request. That's because once the form in the `/posts/new` page is submitted, it becomes a POST request to the create action. Because we have supplied valid attributes, the post should have been successfully created and we should have redirected to the show action of the new post. This new page will have an address like `/posts/ID`, where ID is the identifier of the post in the database. We then use `redirected_params(conn)` to get the ID of the post and then match that we indeed redirected to the show action. Finally, we do request a `get` request to the page we redirected to, allowing us to verify that the post was indeed created. For the second test, we simply test the failure scenario. If any invalid attribute is given, it should re-render the "New Post" page. One common question is: how many failure scenarios do you test at the controller level? For example, in the [Testing Contexts](testing_contexts.html) guide, we introduced a validation to the `title` field of the post: ``` def changeset(post, attrs) do post |> cast(attrs, [:title, :body]) |> validate_required([:title, :body]) |> validate_length(:title, min: 2) end ``` In other words, creating a post can fail for the following reasons: - the title is missing - the body is missing - the title is present but is less than 2 characters Should we test all of these possible outcomes in our controller tests? The answer is no. All of the different rules and outcomes should be verified in your context and schema tests. The controller works as the integration layer. In the controller tests we simply want to verify, in broad strokes, that we handle both success and failure scenarios. The test for `update` follows a similar structure as `create`, so let's skip to the `delete` test. ### [](testing_controllers.html#the-delete-action)The delete action The `delete` action looks like this: ``` def delete(conn, %{"id" => id}) do post = Blog.get_post!(id) {:ok, _post} = Blog.delete_post(post) conn |> put_flash(:info, "Post deleted successfully.") |> redirect(to: ~p"/posts") end ``` The test is written like this: ``` describe "delete post" do setup [:create_post] test "deletes chosen post", %{conn: conn, post: post} do conn = delete(conn, ~p"/posts/#{post}") assert redirected_to(conn) == ~p"/posts" assert_error_sent 404, fn -> get(conn, ~p"/posts/#{post}") end end end defp create_post(_) do post = post_fixture() %{post: post} end ``` First of all, `setup` is used to declare that the `create_post` function should run before every test in this `describe` block. The `create_post` function simply creates a post and stores it in the test metadata. This allows us to, in the first line of the test, match on both the post and the connection: ``` test "deletes chosen post", %{conn: conn, post: post} do ``` The test uses `delete/2` to delete the post and then asserts that we redirected to the index page. Finally, we check that it is no longer possible to access the show page of the deleted post: ``` assert_error_sent 404, fn -> get(conn, ~p"/posts/#{post}") end ``` `assert_error_sent` is a testing helper provided by [`Phoenix.ConnTest`](Phoenix.ConnTest.html). In this case, it verifies that: 1. An exception was raised 2. The exception has a status code equivalent to 404 (which stands for Not Found) This pretty much mimics how Phoenix handles exceptions. For example, when we access `/posts/12345` where `12345` is an ID that does not exist, we will invoke our `show` action: ``` def show(conn, %{"id" => id}) do post = Blog.get_post!(id) render(conn, :show, post: post) end ``` When an unknown post ID is given to `Blog.get_post!/1`, it raises an `Ecto.NotFoundError`. If your application raises any exception during a web request, Phoenix translates those requests into proper HTTP response codes. In this case, 404. We could, for example, have written this test as: ``` assert_raise Ecto.NotFoundError, fn -> get(conn, ~p"/posts/#{post}") end ``` However, you may prefer the implementation Phoenix generates by default as it ignores the specific details of the failure, and instead verifies what the browser would actually receive. The tests for `new`, `edit`, and `show` actions are simpler variations of the tests we have seen so far. You can check the action implementation and their respective tests yourself. Now we are ready to move to JSON controller tests. ## [](testing_controllers.html#json-controller-tests)JSON controller tests So far we have been working with a generated HTML resource. However, let's take a look at how our tests look like when we generate a JSON resource. First of all, run this command: ``` $ mix phx.gen.json News Article articles title body ``` We chose a very similar concept to the Blog context &lt;-&gt; Post schema, except we are using a different name, so we can study these concepts in isolation. After you run the command above, do not forget to follow the final steps output by the generator. Once all is done, we should run [`mix test`](../mix/Mix.Tasks.Test.html) and now have 35 passing tests: ``` $ mix test ................ Finished in 0.6 seconds 35 tests, 0 failures Randomized with seed 618478 ``` You may have noticed that this time the scaffold controller has generated fewer tests. Previously it generated 16 (we went from 5 to 21) and now it generated 14 (we went from 21 to 35). That's because JSON APIs do not need to expose the `new` and `edit` actions. We can see this is the case in the resource we have added to the router at the end of the [`mix phx.gen.json`](Mix.Tasks.Phx.Gen.Json.html) command: ``` resources "/articles", ArticleController, except: [:new, :edit] ``` `new` and `edit` are only necessary for HTML because they basically exist to assist users in creating and updating resources. Besides having less actions, we will notice the controller and view tests and implementations for JSON are drastically different from the HTML ones. The only thing that is pretty much the same between HTML and JSON is the contexts and the schema, which, once you think about it, it makes total sense. After all, your business logic should remain the same, regardless if you are exposing it as HTML or JSON. With the differences in hand, let's take a look at the controller tests. ### [](testing_controllers.html#the-index-action-1)The index action Open up `test/hello_web/controllers/article_controller_test.exs`. The initial structure is quite similar to `post_controller_test.exs`. So let's take a look at the tests for the `index` action. The `index` action itself is implemented in `lib/hello_web/controllers/article_controller.ex` like this: ``` def index(conn, _params) do articles = News.list_articles() render(conn, :index, articles: articles) end ``` The action gets all articles and renders the index template. Since we are talking about JSON, we don't have a `index.json.heex` template. Instead, the code that converts `articles` into JSON can be found directly in the ArticleJSON module, defined at `lib/hello_web/controllers/article_json.ex` like this: ``` defmodule HelloWeb.ArticleJSON do alias Hello.News.Article def index(%{articles: articles}) do %{data: for(article <- articles, do: data(article))} end def show(%{article: article}) do %{data: data(article)} end defp data(%Article{} = article) do %{ id: article.id, title: article.title, body: article.body } end end ``` Since a controller render is a regular function call, we don't need any extra features to render JSON. We simply define functions for our `index` and `show` actions that return the map of JSON for articles. Let's take a look at the test for the `index` action then: ``` describe "index" do test "lists all articles", %{conn: conn} do conn = get(conn, ~p"/api/articles") assert json_response(conn, 200)["data"] == [] end end ``` It simply accesses the `index` path, asserts we got a JSON response with status 200 and that it contains a "data" key with an empty list, as we have no articles to return. That was quite boring. Let's look at something more interesting. ### [](testing_controllers.html#the-create-action-1)The `create` action The `create` action is defined like this: ``` def create(conn, %{"article" => article_params}) do with {:ok, %Article{} = article} <- News.create_article(article_params) do conn |> put_status(:created) |> put_resp_header("location", ~p"/api/articles/#{article}") |> render(:show, article: article) end end ``` As we can see, it checks if an article could be created. If so, it sets the status code to `:created` (which translates to 201), it sets a "location" header with the location of the article, and then renders "show.json" with the article. This is precisely what the first test for the `create` action verifies: ``` describe "create article" do test "renders article when data is valid", %{conn: conn} do conn = post(conn, ~p"/articles", article: @create_attrs) assert %{"id" => id} = json_response(conn, 201)["data"] conn = get(conn, ~p"/api/articles/#{id}") assert %{ "id" => ^id, "body" => "some body", "title" => "some title" } = json_response(conn, 200)["data"] end ``` The test uses `post/2` to create a new article and then we verify that the article returned a JSON response, with status 201, and that it had a "data" key in it. We pattern match the "data" on `%{"id" => id}`, which allows us to extract the ID of the new article. Then we perform a `get/2` request on the `show` route and verify that the article was successfully created. Inside `describe "create article"`, we will find another test, which handles the failure scenario. Can you spot the failure scenario in the `create` action? Let's recap it: ``` def create(conn, %{"article" => article_params}) do with {:ok, %Article{} = article} <- News.create_article(article_params) do ``` The `with` special form that ships as part of Elixir allows us to check explicitly for the happy paths. In this case, we are interested only in the scenarios where `News.create_article(article_params)` returns `{:ok, article}`, if it returns anything else, the other value will simply be returned directly and none of the contents inside the `do/end` block will be executed. In other words, if `News.create_article/1` returns `{:error, changeset}`, we will simply return `{:error, changeset}` from the action. However, this introduces an issue. Our actions do not know how to handle the `{:error, changeset}` result by default. Luckily, we can teach Phoenix Controllers to handle it with the Action Fallback controller. At the top of `ArticleController`, you will find: ``` action_fallback HelloWeb.FallbackController ``` This line says: if any action does not return a `%Plug.Conn{}`, we want to invoke `FallbackController` with the result. You will find `HelloWeb.FallbackController` at `lib/hello_web/controllers/fallback_controller.ex` and it looks like this: ``` defmodule HelloWeb.FallbackController do use HelloWeb, :controller def call(conn, {:error, %Ecto.Changeset{} = changeset}) do conn |> put_status(:unprocessable_entity) |> put_view(json: HelloWeb.ChangesetJSON) |> render(:error, changeset: changeset) end def call(conn, {:error, :not_found}) do conn |> put_status(:not_found) |> put_view(html: HelloWeb.ErrorHTML, json: HelloWeb.ErrorJSON) |> render(:"404") end end ``` You can see how the first clause of the `call/2` function handles the `{:error, changeset}` case, setting the status code to unprocessable entity (422), and then rendering "error.json" from the changeset view with the failed changeset. With this in mind, let's look at our second test for `create`: ``` test "renders errors when data is invalid", %{conn: conn} do conn = post(conn, ~p"/api/articles", article: @invalid_attrs) assert json_response(conn, 422)["errors"] != %{} end ``` It simply posts to the `create` path with invalid parameters. This makes it return a JSON response, with status code 422, and a response with a non-empty "errors" key. The `action_fallback` can be extremely useful to reduce boilerplate when designing APIs. You can learn more about the "Action Fallback" in the [Controllers guide](controllers.html). ### [](testing_controllers.html#the-delete-action-1)The `delete` action Finally, the last action we will study is the `delete` action for JSON. Its implementation looks like this: ``` def delete(conn, %{"id" => id}) do article = News.get_article!(id) with {:ok, %Article{}} <- News.delete_article(article) do send_resp(conn, :no_content, "") end end ``` The new action simply attempts to delete the article and, if it succeeds, it returns an empty response with status code `:no_content` (204). The test looks like this: ``` describe "delete article" do setup [:create_article] test "deletes chosen article", %{conn: conn, article: article} do conn = delete(conn, ~p"/api/articles/#{article}") assert response(conn, 204) assert_error_sent 404, fn -> get(conn, ~p"/api/articles/#{article}") end end end defp create_article(_) do article = article_fixture() %{article: article} end ``` It setups a new article, then in the test it invokes the `delete` path to delete it, asserting on a 204 response, which is neither JSON nor HTML. Then it verifies that we can no longer access said article. That's all! Now that we understand how the scaffolded code and their tests work for both HTML and JSON APIs, we are prepared to move forward in building and maintaining our web applications! [← Previous Page Testing Contexts](testing_contexts.html) [Next Page → Testing Channels](testing_channels.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/testing/testing_controllers.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/up_and_running.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/introduction/up_and_running.md#L1 "View Source") Up and Running There are two mechanisms to start a new Phoenix application: the express option, supported on some OSes, and via [`mix phx.new`](Mix.Tasks.Phx.New.html). Let's check it out. ## [](up_and_running.html#phoenix-express)Phoenix Express A single command will get you up and running in seconds: For macOS/Ubuntu: ``` $ curl https://new.phoenixframework.org/myapp | sh ``` For Windows PowerShell: ``` > curl.exe -fsSO https://new.phoenixframework.org/myapp.bat; .\myapp.bat ``` The above will install Erlang, Elixir, and Phoenix, and generate a fresh Phoenix application. It will also automatically pick one of PostgreSQL or MySQL as the database, and fallback to SQLite if none of them are available. Once the command above, it will open up a Phoenix application, with the steps necessary to complete your installation. > Your Phoenix application name is taken from the path. If your operating system is not supported, or the command above fails, don't fret! You can still start your Phoenix application using [`mix phx.new`](Mix.Tasks.Phx.New.html). ## [](up_and_running.html#via-mix-phx-new)Via [`mix phx.new`](Mix.Tasks.Phx.New.html) In order to create a new Phoenix application, you will need to install Erlang, Elixir, and Phoenix. See the [Installation Guide](installation.html) for more information. If you share your application with someone, they will also need to follow the Installation Guide steps to set it all up. Once you are ready, you can run [`mix phx.new`](Mix.Tasks.Phx.New.html) from any directory in order to bootstrap our Phoenix application. Phoenix will accept either an absolute or relative path for the directory of our new project. Assuming that the name of our application is `hello`, let's run the following command: ``` $ mix phx.new hello ``` > By default, [`mix phx.new`](Mix.Tasks.Phx.New.html) includes a number of optional dependencies, for example: > > - [Ecto](ecto.html) for communicating with a data store, such as PostgreSQL, MySQL, and others. You can skip this with `--no-ecto`. > - [Phoenix.HTML](../phoenix_html/Phoenix.HTML.html), [TailwindCSS](https://tailwindcss.com), and [Esbuild](https://esbuild.github.io) for HTML applications. You can skip them with the `--no-html` and `--no-assets` flags. > - [Phoenix.LiveView](../phoenix_live_view/index.html) for building realtime and interactive web applications. You can skip this with `--no-live`. > > Read the [Mix Tasks Guide](mix_tasks.html#phoenix-specific-mix-tasks) for the full list of things that can be excluded, among other options. ``` mix phx.new hello * creating hello/config/config.exs * creating hello/config/dev.exs * creating hello/config/prod.exs ... Fetch and install dependencies? [Yn] ``` Phoenix generates the directory structure and all the files we will need for our application. > Phoenix promotes the usage of git as version control software: among the generated files we find a `.gitignore`. We can `git init` our repository, and immediately add and commit all that hasn't been marked ignored. When it's done, it will ask us if we want it to install our dependencies for us. Let's say yes to that. ``` Fetch and install dependencies? [Yn] Y * running mix deps.get * running mix assets.setup * running mix deps.compile We are almost there! The following steps are missing: $ cd hello Then configure your database in config/dev.exs and run: $ mix ecto.create Start your Phoenix app with: $ mix phx.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phx.server ``` Once our dependencies are installed, the task will prompt us to change into our project directory and start our application. Phoenix assumes that our PostgreSQL database will have a `postgres` user account with the correct permissions and a password of "postgres". If that isn't the case, please see the [Mix Tasks Guide](mix_tasks.html#ecto-specific-mix-tasks) to learn more about the [`mix ecto.create`](../ecto/3.10.1/Mix.Tasks.Ecto.Create.html) task. Ok, let's give it a try. First, we'll `cd` into the `hello/` directory we've just created: ``` $ cd hello ``` Now we'll create our database: ``` $ mix ecto.create Compiling 13 files (.ex) Generated hello app The database for Hello.Repo has been created ``` In case the database could not be created, see the guides for the [`mix ecto.create`](mix_tasks.html#mix-ecto-create) for general troubleshooting. > Note: if this is the first time you are running this command, Phoenix may also ask to install Rebar. Go ahead with the installation as Rebar is used to build Erlang packages. And finally, we'll start the Phoenix server: ``` $ mix phx.server [info] Running HelloWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http) [info] Access HelloWeb.Endpoint at http://localhost:4000 [watch] build finished, watching for changes... ... ``` If we choose not to have Phoenix install our dependencies when we generate a new application, the [`mix phx.new`](Mix.Tasks.Phx.New.html) task will prompt us to take the necessary steps when we do want to install them. ``` Fetch and install dependencies? [Yn] n We are almost there! The following steps are missing: $ cd hello $ mix deps.get Then configure your database in config/dev.exs and run: $ mix ecto.create Start your Phoenix app with: $ mix phx.server You can also run your app inside IEx (Interactive Elixir) as: $ iex -S mix phx.server ``` By default, Phoenix accepts requests on port 4000. If we point our favorite web browser at [http://localhost:4000](http://localhost:4000), we should see the Phoenix Framework welcome page. ![Phoenix Welcome Page](assets/images/welcome-to-phoenix.png) If your screen looks like the image above, congratulations! You now have a working Phoenix application. In case you can't see the page above, try accessing it via [http://127.0.0.1:4000](http://127.0.0.1:4000) and later make sure your OS has defined "localhost" as "127.0.0.1". To stop it, we hit `ctrl-c` twice. Now you are ready to explore the world provided by Phoenix! See [our community page](community.html) for books, screencasts, courses, and more. Alternatively, you can continue reading these guides to have a quick introduction into all the parts that make your Phoenix application. If that's the case, you can read the guides in any order or start with our guide that explains the [Phoenix directory structure](directory_structure.html). [← Previous Page Installation](installation.html) [Next Page → Community](community.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/introduction/up_and_running.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/using_ssl.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/howto/using_ssl.md#L1 "View Source") Using SSL To prepare an application to serve requests over SSL, we need to add a little bit of configuration and two environment variables. In order for SSL to actually work, we'll need a key file and certificate file from a certificate authority. The environment variables that we'll need are paths to those two files. The configuration consists of a new `https:` key for our endpoint whose value is a keyword list of port, path to the key file, and path to the cert (PEM) file. If we add the `otp_app:` key whose value is the name of our application, Plug will begin to look for them at the root of our application. We can then put those files in our `priv` directory and set the paths to `priv/our_keyfile.key` and `priv/our_cert.crt`. Here's an example configuration from `config/runtime.exs`. ``` import Config config :hello, HelloWeb.Endpoint, http: [port: {:system, "PORT"}], url: [host: "example.com"], cache_static_manifest: "priv/static/cache_manifest.json", https: [ port: 443, cipher_suite: :strong, otp_app: :hello, keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), certfile: System.get_env("SOME_APP_SSL_CERT_PATH"), # OPTIONAL Key for intermediate certificates: cacertfile: System.get_env("INTERMEDIATE_CERTFILE_PATH") ] ``` Without the `otp_app:` key, we need to provide absolute paths to the files wherever they are on the filesystem in order for Plug to find them. ``` Path.expand("../../../some/path/to/ssl/key.pem", __DIR__) ``` The options under the `https:` key are passed to the Plug adapter, typically `Bandit`, which in turn uses [`Plug.SSL`](../plug/1.15.3/Plug.SSL.html) to select the TLS socket options. Please refer to the documentation for [Plug.SSL.configure/1](https://hexdocs.pm/plug/Plug.SSL.html#configure/1) for more information on the available options and their defaults. The [Plug HTTPS Guide](https://hexdocs.pm/plug/https.html) and the [Erlang/OTP ssl](https://www.erlang.org/doc/man/ssl.html) documentation also provide valuable information. ## [](using_ssl.html#ssl-in-development)SSL in Development If you would like to use HTTPS in development, a self-signed certificate can be generated by running: [`mix phx.gen.cert`](Mix.Tasks.Phx.Gen.Cert.html). This requires Erlang/OTP 20 or later. With your self-signed certificate, your development configuration in `config/dev.exs` can be updated to run an HTTPS endpoint: ``` config :my_app, MyAppWeb.Endpoint, ... https: [ port: 4001, cipher_suite: :strong, keyfile: "priv/cert/selfsigned_key.pem", certfile: "priv/cert/selfsigned.pem" ] ``` This can replace your `http` configuration, or you can run HTTP and HTTPS servers on different ports. ## [](using_ssl.html#force-ssl)Force SSL In many cases, you'll want to force all incoming requests to use SSL by redirecting HTTP to HTTPS. This can be accomplished by setting the `:force_ssl` option in your endpoint configuration. It expects a list of options which are forwarded to [`Plug.SSL`](../plug/1.15.3/Plug.SSL.html). By default, it sets the "strict-transport-security" header in HTTPS requests, forcing browsers to always use HTTPS. If an unsafe (HTTP) request is sent, it redirects to the HTTPS version using the `:host` specified in the `:url` configuration. For example: ``` config :my_app, MyAppWeb.Endpoint, force_ssl: [rewrite_on: [:x_forwarded_proto]] ``` To dynamically redirect to the `host` of the current request, set `:host` in the `:force_ssl` configuration to `nil`. ``` config :my_app, MyAppWeb.Endpoint, force_ssl: [rewrite_on: [:x_forwarded_proto], host: nil] ``` In these examples, the `rewrite_on:` key specifies the HTTP header used by a reverse proxy or load balancer in front of the application to indicate whether the request was received over HTTP or HTTPS. For more information on the implications of offloading TLS to an external element, in particular relating to secure cookies, refer to the [Plug HTTPS Guide](https://hexdocs.pm/plug/https.html#offloading-tls). Keep in mind that the options passed to [`Plug.SSL`](../plug/1.15.3/Plug.SSL.html) in that document should be set using the `force_ssl:` endpoint option in a Phoenix application. It is important to note that `force_ssl:` is a *compile* time config, so it normally is set in `prod.exs`, it will not work when set from `runtime.exs`. ## [](using_ssl.html#hsts)HSTS HSTS, short for 'HTTP Strict-Transport-Security', is a mechanism that allows websites to declare themselves as accessible exclusively through a secure connection (HTTPS). It was introduced to prevent man-in-the-middle attacks that strip SSL/TLS encryption. HSTS causes web browsers to redirect from HTTP to HTTPS and to refuse to connect unless the connection uses SSL/TLS. With `force_ssl: [hsts: true]` set, the `Strict-Transport-Security` header is added with a max-age that defines the duration for which the policy is valid. Modern web browsers will respond to this by redirecting from HTTP to HTTPS, among other consequences. [RFC6797](https://tools.ietf.org/html/rfc6797), which defines HSTS, also specifies that **the browser should keep track of a host's policy and apply it until it expires.** It further specifies that **traffic on any port other than 80 is assumed to be encrypted** as per the policy. While HSTS is recommended in production, it can lead to unexpected behavior when accessing applications on localhost. For instance, accessing an application with HSTS enabled at `https://localhost:4000` leads to a situation where all subsequent traffic from localhost, except for port 80, is expected to be encrypted. This can disrupt traffic to other local servers or proxies running on your computer that are unrelated to your Phoenix application and may not support encrypted traffic. If you inadvertently enable HSTS for localhost, you may need to reset your browser's cache before it will accept HTTP traffic from localhost again. For Chrome: 1. Open the Developer Tools Panel. 2. Click and hold the reload icon next to the address bar to reveal a dropdown menu. 3. Select "Empty Cache and Hard Reload". For Safari: 1. Clear your browser cache. 2. Remove the entry from `~/Library/Cookies/HSTS.plist` or delete the file entirely. 3. Restart Safari. For other browsers, please consult the documentation for HSTS. Alternatively, setting the `:expires` option on `force_ssl` to `0` should expire the entry and disable HSTS. For more information on HSTS options, see [Plug.SSL](https://hexdocs.pm/plug/Plug.SSL.html). [← Previous Page File Uploads](file_uploads.html) [Next Page → Writing a Channels Client](writing_a_channels_client.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/howto/using_ssl.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix/writing_a_channels_client.html.md === [![Phoenix](assets/logo.png)](https://www.phoenixframework.org) [Phoenix](https://www.phoenixframework.org) v1.7.18 - GUIDES - Modules - Mix Tasks <!--THE END--> <!--THE END--> <!--THE END--> Search documentation of Phoenix Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.18/guides/howto/writing_a_channels_client.md#L1 "View Source") Writing a Channels Client Client libraries for Phoenix Channels already exist in [several languages](channels.html#client-libraries), but if you want to write your own, this guide should get you started. It may also be useful as a guide for manual testing with a WebSocket client. ## [](writing_a_channels_client.html#overview)Overview Because WebSockets are bidirectional, messages can flow in either direction at any time. For this reason, clients typically use callbacks to handle incoming messages whenever they come. A client must join at least one topic to begin sending and receiving messages, and may join any number of topics using the same connection. ## [](writing_a_channels_client.html#connecting)Connecting To establish a WebSocket connection to Phoenix Channels, first make note of the `socket` declaration in the application's `Endpoint` module. For example, if you see: `socket "/mobile", MyAppWeb.MobileSocket`, the path for the initial HTTP request is: ``` [host]:[port]/mobile/websocket?vsn=2.0.0 ``` Passing `&vsn=2.0.0` specifies `Phoenix.Socket.V2.JSONSerializer`, which is built into Phoenix, and which expects and returns messages in the form of lists. You also need to include [the standard header fields for upgrading an HTTP request to a WebSocket connection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) or use an HTTP library that handles this for you; in Elixir, [mint\_web\_socket](https://hex.pm/packages/mint_web_socket) is an example. Other parameters or headers may be expected or required by the specific `connect/3` function in the application's socket module (in the example above, `MyAppWeb.MobileSocket.connect/3`). ## [](writing_a_channels_client.html#message-format)Message Format The message format is determined by the serializer configured for the application. For these examples, `Phoenix.Socket.V2.JSONSerializer` is assumed. The general format for messages a client sends to a Phoenix Channel is as follows: ``` [join_reference, message_reference, topic_name, event_name, payload] ``` - The `join_reference` is also chosen by the client and should also be a unique value. It only needs to be sent for a `"phx_join"` event; for other messages it can be `null`. It is used as a message reference for `push` messages from the server, meaning those that are not replies to a specific client message. For example, imagine something like "a new user just joined the chat room". - The `message_reference` is chosen by the client and should be a unique value. The server includes it in its reply so that the client knows which message the reply is for. - The `topic_name` must be a known topic for the socket endpoint, and a client must join that topic before sending any messages on it. - The `event_name` must match the first argument of a `handle_in` function on the server channel module. - The `payload` should be a map and is passed as the second argument to that `handle_in` function. There are three events that are understood by every Phoenix application. First, `phx_join` is used join a channel. For example, to join the `miami:weather` channel: ``` ["0", "0", "miami:weather", "phx_join", {"some": "param"}] ``` Second, `phx_leave` is used to leave a channel. For example, to leave the `miami:weather` channel: ``` [null, "1", "miami:weather", "phx_leave", {}] ``` Third, `heartbeat` is used to maintain the WebSocket connection. For example: ``` [null, "2", "phoenix", "heartbeat", {}] ``` The `heartbeat` message is only needed when no other messages are being sent and prevents Phoenix from closing the connection; the exact `:timeout` is configured in the application's `Endpoint` module. Other allowed messages depend on the Phoenix application. For example, if the Channel serving the `miami:weather` can handle a `report_emergency` event: ``` def handle_in("report_emergency", payload, socket) do MyApp.Emergencies.report(payload) # or whatever {:reply, :ok, socket} end ``` ...a client could send: ``` [null, "3", "miami:weather", "report_emergency", {"category": "sharknado"}] ``` [← Previous Page Using SSL](using_ssl.html) [Hex Package](https://hex.pm/packages/phoenix/1.7.18) [Hex Preview](https://preview.hex.pm/preview/phoenix/1.7.18) ([current file](https://preview.hex.pm/preview/phoenix/1.7.18/show/guides/howto/writing_a_channels_client.md)) Search HexDocs [Download ePub version](Phoenix.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.35.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_html/Phoenix.HTML.Form.html.md === [Phoenix.HTML](Phoenix.HTML.html) v4.2.0 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix.HTML Settings # [View Source](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L1 "View Source") Phoenix.HTML.Form (Phoenix.HTML v4.2.0) Define a [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html) struct and functions to interact with it. For building actual forms in your Phoenix application, see [the `Phoenix.Component.form/1` component](../phoenix_live_view/Phoenix.Component.html#form/1). ## [](Phoenix.HTML.Form.html#module-access-behaviour)Access behaviour The [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html) struct implements the [`Access`](../elixir/Access.html) behaviour. When you do `form[field]`, it returns a [`Phoenix.HTML.FormField`](Phoenix.HTML.FormField.html) struct with the `id`, `name`, `value`, and `errors` prefilled. The field name can be either an atom or a string. If it is an atom, it assumes the form keeps both data and errors as atoms. If it is a string, it considers that data and errors are stored as strings for said field. Forms backed by an `Ecto.Changeset` only support atom field names. It is possible to "access" fields which do not exist in the source data structure. A [`Phoenix.HTML.FormField`](Phoenix.HTML.FormField.html) struct will be dynamically created with some attributes such as `name` and `id` populated. ## [](Phoenix.HTML.Form.html#module-custom-implementations)Custom implementations There is a protocol named [`Phoenix.HTML.FormData`](Phoenix.HTML.FormData.html) which can be implemented by any data structure that wants to be cast to the [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html) struct. # [](Phoenix.HTML.Form.html#summary)Summary ## [Types](Phoenix.HTML.Form.html#types) [field()](Phoenix.HTML.Form.html#t:field/0) [t()](Phoenix.HTML.Form.html#t:t/0) ## [Functions](Phoenix.HTML.Form.html#functions) [%Phoenix.HTML.Form{}](Phoenix.HTML.Form.html#__struct__/0) Defines the Phoenix.HTML.Form struct. [input\_changed?(form1, form2, field)](Phoenix.HTML.Form.html#input_changed?/3) Receives two forms structs and checks if the given field changed. [input\_id(name, field)](Phoenix.HTML.Form.html#input_id/2) Returns an id of a corresponding form field. [input\_id(name, field, value)](Phoenix.HTML.Form.html#input_id/3) Returns an id of a corresponding form field and value attached to it. [input\_name(form\_or\_name, field)](Phoenix.HTML.Form.html#input_name/2) Returns a name of a corresponding form field. [input\_validations(form, field)](Phoenix.HTML.Form.html#input_validations/2) Returns the HTML validations that would apply to the given field. [input\_value(form, field)](Phoenix.HTML.Form.html#input_value/2) Returns a value of a corresponding form field. [normalize\_value(arg1, value)](Phoenix.HTML.Form.html#normalize_value/2) Normalizes an input `value` according to its input `type`. [options\_for\_select(options, selected\_values)](Phoenix.HTML.Form.html#options_for_select/2) Returns options to be used inside a select. # [](Phoenix.HTML.Form.html#types)Types [](Phoenix.HTML.Form.html#t:field/0) # field() [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L91) ``` @type field() :: atom() | String.t() ``` [](Phoenix.HTML.Form.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L77) ``` @type t() :: %Phoenix.HTML.Form{ action: atom(), data: %{required(field()) => term()}, errors: [{field(), term()}], hidden: Keyword.t(), id: String.t(), impl: module(), index: nil | non_neg_integer(), name: String.t(), options: Keyword.t(), params: %{required(binary()) => term()}, source: Phoenix.HTML.FormData.t() } ``` # [](Phoenix.HTML.Form.html#functions)Functions [](Phoenix.HTML.Form.html#__struct__/0) # %Phoenix.HTML.Form{} [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L0)(struct) Defines the Phoenix.HTML.Form struct. Its fields are: - `:source` - the data structure that implements the form data protocol - `:action` - The action that was taken against the form. This value can be used to distinguish between different operations such as the user typing into a form for validation, or submitting a form for a database insert. - `:impl` - the module with the form data protocol implementation. This is used to avoid multiple protocol dispatches. - `:id` - the id to be used when generating input fields - `:index` - the index of the struct in the form - `:name` - the name to be used when generating input fields - `:data` - the field used to store lookup data - `:params` - the parameters associated with this form - `:hidden` - a keyword list of fields that are required to submit the form behind the scenes as hidden inputs - `:options` - a copy of the options given when creating the form without any form data specific key - `:errors` - a keyword list of errors that are associated with the form [](Phoenix.HTML.Form.html#input_changed?/3) # input\_changed?(form1, form2, field) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L203) ``` @spec input_changed?(t(), t(), field()) :: boolean() ``` Receives two forms structs and checks if the given field changed. The field will have changed if either its associated value, errors, action, or implementation changed. This is mostly used for optimization engines as an extension of the [`Access`](../elixir/Access.html) behaviour. [](Phoenix.HTML.Form.html#input_id/2) # input\_id(name, field) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L152) ``` @spec input_id(t() | atom(), field()) :: String.t() ``` Returns an id of a corresponding form field. The form should either be a [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html) or an atom. [](Phoenix.HTML.Form.html#input_id/3) # input\_id(name, field, value) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L168) ``` @spec input_id(t() | atom(), field(), Phoenix.HTML.Safe.t()) :: String.t() ``` Returns an id of a corresponding form field and value attached to it. Useful for radio buttons and inputs like multiselect checkboxes. [](Phoenix.HTML.Form.html#input_name/2) # input\_name(form\_or\_name, field) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L185) ``` @spec input_name(t() | atom(), field()) :: String.t() ``` Returns a name of a corresponding form field. The first argument should either be a [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html) or an atom. ## [](Phoenix.HTML.Form.html#input_name/2-examples)Examples ``` iex> Phoenix.HTML.Form.input_name(:user, :first_name) "user[first_name]" ``` [](Phoenix.HTML.Form.html#input_validations/2) # input\_validations(form, field) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L233) ``` @spec input_validations(t(), field()) :: Keyword.t() ``` Returns the HTML validations that would apply to the given field. [](Phoenix.HTML.Form.html#input_value/2) # input\_value(form, field) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L139) ``` @spec input_value(t() | atom(), field()) :: term() ``` Returns a value of a corresponding form field. The `form` should either be a [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html) or an atom. The field is either a string or an atom. If the field is given as an atom, it will attempt to look data with atom keys. If a string, it will look data with string keys. When a form is given, it will look for changes, then fallback to parameters, and finally fallback to the default struct/map value. Since the function looks up parameter values too, there is no guarantee that the value will have a certain type. For example, a boolean field will be sent as "false" as a parameter, and this function will return it as is. If you need to normalize the result of `input_value`, see [`normalize_value/2`](Phoenix.HTML.Form.html#normalize_value/2). [](Phoenix.HTML.Form.html#normalize_value/2) # normalize\_value(arg1, value) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L256) Normalizes an input `value` according to its input `type`. Certain HTML input values must be cast, or they will have idiosyncracies when they are rendered. The goal of this function is to encapsulate this logic. In particular: - For "datetime-local" types, it converts [`DateTime`](../elixir/DateTime.html) and [`NaiveDateTime`](../elixir/NaiveDateTime.html) to strings without the second precision - For "checkbox" types, it returns a boolean depending on whether the input is "true" or not - For "textarea", it prefixes a newline to ensure newlines won't be ignored on submission. This requires however that the textarea is rendered with no spaces after its content [](Phoenix.HTML.Form.html#options_for_select/2) # options\_for\_select(options, selected\_values) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form.ex#L320) Returns options to be used inside a select. This is useful when building the select by hand. It expects all options and one or more select values. ## [](Phoenix.HTML.Form.html#options_for_select/2-examples)Examples ``` options_for_select(["Admin": "admin", "User": "user"], "admin") #=> <option value="admin" selected>Admin</option> #=> <option value="user">User</option> ``` Multiple selected values: ``` options_for_select(["Admin": "admin", "User": "user", "Moderator": "moderator"], ["admin", "moderator"]) #=> <option value="admin" selected>Admin</option> #=> <option value="user">User</option> #=> <option value="moderator" selected>Moderator</option> ``` Groups are also supported: ``` options_for_select(["Europe": ["UK", "Sweden", "France"], ...], nil) #=> <optgroup label="Europe"> #=> <option>UK</option> #=> <option>Sweden</option> #=> <option>France</option> #=> </optgroup> ``` Horizontal separators can be added: ``` options_for_select(["Admin", "User", :hr, "New"], nil) #=> <option>Admin</option> #=> <option>User</option> #=> <hr/> #=> <option>New</option> options_for_select(["Admin": "admin", "User": "user", hr: nil, "New": "new"], nil) #=> <option value="admin" selected>Admin</option> #=> <option value="user">User</option> #=> <hr/> #=> <option value="new">New</option> ``` [Hex Package](https://hex.pm/packages/phoenix_html/4.2.0) [Hex Preview](https://preview.hex.pm/preview/phoenix_html/4.2.0) Search HexDocs [Download ePub version](Phoenix.HTML.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_html/Phoenix.HTML.FormData.html.md === [Phoenix.HTML](Phoenix.HTML.html) v4.2.0 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix.HTML Settings # [View Source](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form_data.ex#L1 "View Source") Phoenix.HTML.FormData protocol (Phoenix.HTML v4.2.0) Converts a data structure into a [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html) struct. ## [](Phoenix.HTML.FormData.html#module-ecto-integration)Ecto integration Phoenix provides integration of forms with Ecto changesets and data structures via the [phoenix\_ecto](https://hex.pm/packages/phoenix_ecto) package. If a project was generated without Ecto support that dependency will need to be manually added. # [](Phoenix.HTML.FormData.html#summary)Summary ## [Types](Phoenix.HTML.FormData.html#types) [t()](Phoenix.HTML.FormData.html#t:t/0) All the types that implement this protocol. ## [Functions](Phoenix.HTML.FormData.html#functions) [input\_validations(data, form, field)](Phoenix.HTML.FormData.html#input_validations/3) Returns the HTML5 validations that would apply to the given field. [input\_value(data, form, field)](Phoenix.HTML.FormData.html#input_value/3) Returns the value for the given field. [to\_form(data, options)](Phoenix.HTML.FormData.html#to_form/2) Converts a data structure into a [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html#t:t/0) struct. [to\_form(data, form, field, options)](Phoenix.HTML.FormData.html#to_form/4) Converts the field in the given form based on the data structure into a list of [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html#t:t/0) structs. # [](Phoenix.HTML.FormData.html#types)Types [](Phoenix.HTML.FormData.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form_data.ex#L1) ``` @type t() :: term() ``` All the types that implement this protocol. # [](Phoenix.HTML.FormData.html#functions)Functions [](Phoenix.HTML.FormData.html#input_validations/3) # input\_validations(data, form, field) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form_data.ex#L77) ``` @spec input_validations(t(), Phoenix.HTML.Form.t(), Phoenix.HTML.Form.field()) :: Keyword.t() ``` Returns the HTML5 validations that would apply to the given field. [](Phoenix.HTML.FormData.html#input_value/3) # input\_value(data, form, field) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form_data.ex#L70) ``` @spec input_value(t(), Phoenix.HTML.Form.t(), Phoenix.HTML.Form.field()) :: term() ``` Returns the value for the given field. [](Phoenix.HTML.FormData.html#to_form/2) # to\_form(data, options) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form_data.ex#L30) ``` @spec to_form(t(), Keyword.t()) :: Phoenix.HTML.Form.t() ``` Converts a data structure into a [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html#t:t/0) struct. The options have their meaning defined by the underlying implementation but all shared options below are expected to be implemented. All remaining options must be stored in the returned struct. ## [](Phoenix.HTML.FormData.html#to_form/2-shared-options)Shared options - `:as` - the value to be used as the form name - `:id` - the ID of the form attribute. All form inputs will be prefixed by the given ID [](Phoenix.HTML.FormData.html#to_form/4) # to\_form(data, form, field, options) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form_data.ex#L64) ``` @spec to_form(t(), Phoenix.HTML.Form.t(), Phoenix.HTML.Form.field(), Keyword.t()) :: [ Phoenix.HTML.Form.t() ] ``` Converts the field in the given form based on the data structure into a list of [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html#t:t/0) structs. The options have their meaning defined by the underlying implementation but all shared options below are expected to be implemented. All remaining options must be stored in the returned struct. ## [](Phoenix.HTML.FormData.html#to_form/4-shared-options)Shared Options - `:id` - the id to be used in the form, defaults to the concatenation of the given `field` to the parent form id - `:as` - the name to be used in the form, defaults to the concatenation of the given `field` to the parent form name - `:default` - the value to use if none is available - `:prepend` - the values to prepend when rendering. This only applies if the field value is a list and no parameters were sent through the form. - `:append` - the values to append when rendering. This only applies if the field value is a list and no parameters were sent through the form. - `:action` - The user defined action being taken by the form, such as `:validate`, `:save`, etc. [Hex Package](https://hex.pm/packages/phoenix_html/4.2.0) [Hex Preview](https://preview.hex.pm/preview/phoenix_html/4.2.0) Search HexDocs [Download ePub version](Phoenix.HTML.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_html/Phoenix.HTML.FormField.html.md === [Phoenix.HTML](Phoenix.HTML.html) v4.2.0 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix.HTML Settings # [View Source](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/form_field.ex#L1 "View Source") Phoenix.HTML.FormField (Phoenix.HTML v4.2.0) The struct returned by `form[field]`. It has the following fields: - `:errors` - a list of errors belonging to the field - `:field` - the field name as an atom or a string - `:form` - the parent `form` struct - `:id` - the `id` to be used as form input as a string - `:name` - the `name` to be used as form input as a string - `:value` - the value for the input [Hex Package](https://hex.pm/packages/phoenix_html/4.2.0) [Hex Preview](https://preview.hex.pm/preview/phoenix_html/4.2.0) Search HexDocs [Download ePub version](Phoenix.HTML.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_html/Phoenix.HTML.Safe.html.md === [Phoenix.HTML](Phoenix.HTML.html) v4.2.0 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix.HTML Settings # [View Source](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/safe.ex#L1 "View Source") Phoenix.HTML.Safe protocol (Phoenix.HTML v4.2.0) Defines the HTML safe protocol. In order to promote HTML safety, Phoenix templates do not use [`Kernel.to_string/1`](../elixir/Kernel.html#to_string/1) to convert data types to strings in templates. Instead, Phoenix uses this protocol which must be implemented by data structures and guarantee that a HTML safe representation is returned. Furthermore, this protocol relies on iodata, which provides better performance when sending or streaming data to the client. # [](Phoenix.HTML.Safe.html#summary)Summary ## [Types](Phoenix.HTML.Safe.html#types) [t()](Phoenix.HTML.Safe.html#t:t/0) All the types that implement this protocol. ## [Functions](Phoenix.HTML.Safe.html#functions) [to\_iodata(data)](Phoenix.HTML.Safe.html#to_iodata/1) # [](Phoenix.HTML.Safe.html#types)Types [](Phoenix.HTML.Safe.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/safe.ex#L1) ``` @type t() :: term() ``` All the types that implement this protocol. # [](Phoenix.HTML.Safe.html#functions)Functions [](Phoenix.HTML.Safe.html#to_iodata/1) # to\_iodata(data) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html/safe.ex#L15) [Hex Package](https://hex.pm/packages/phoenix_html/4.2.0) [Hex Preview](https://preview.hex.pm/preview/phoenix_html/4.2.0) Search HexDocs [Download ePub version](Phoenix.HTML.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_html/Phoenix.HTML.html.md === [Phoenix.HTML](Phoenix.HTML.html) v4.2.0 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix.HTML Settings # [View Source](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L1 "View Source") Phoenix.HTML (Phoenix.HTML v4.2.0) Building blocks for working with HTML in Phoenix. This library provides three main functionalities: - HTML safety - Form abstractions - A tiny JavaScript library to enhance applications ## [](Phoenix.HTML.html#module-html-safety)HTML safety One of the main responsibilities of this package is to provide convenience functions for escaping and marking HTML code as safe. By default, data output in templates is not considered safe: ``` <%= "<hello>" %> ``` will be shown as: ``` &lt;hello&gt; ``` User data or data coming from the database is almost never considered safe. However, in some cases, you may want to tag it as safe and show its "raw" contents: ``` <%= raw "<hello>" %> ``` ## [](Phoenix.HTML.html#module-form-handling)Form handling See [`Phoenix.HTML.Form`](Phoenix.HTML.Form.html). ## [](Phoenix.HTML.html#module-javascript-library)JavaScript library This project ships with a tiny bit of JavaScript that listens to all click events to: - Support `data-confirm="message"` attributes, which shows a confirmation modal with the given message - Support `data-method="patch|post|put|delete"` attributes, which sends the current click as a PATCH/POST/PUT/DELETE HTTP request. You will need to add `data-to` with the URL and `data-csrf` with the CSRF token value - Dispatch a "phoenix.link.click" event. You can listen to this event to customize the behaviour above. Returning false from this event will disable `data-method`. Stopping propagation will disable `data-confirm` To use the functionality above, you must load `priv/static/phoenix_html.js` into your build tool. ### [](Phoenix.HTML.html#module-overriding-the-default-confirmation-behaviour)Overriding the default confirmation behaviour You can override the default implementation by hooking into `phoenix.link.click`. Here is an example: ``` window.addEventListener('phoenix.link.click', function (e) { // Introduce custom behaviour var message = e.target.getAttribute("data-prompt"); var answer = e.target.getAttribute("data-prompt-answer"); if(message && answer && (answer != window.prompt(message))) { e.preventDefault(); } }, false); ``` # [](Phoenix.HTML.html#summary)Summary ## [Types](Phoenix.HTML.html#types) [safe()](Phoenix.HTML.html#t:safe/0) Guaranteed to be safe [unsafe()](Phoenix.HTML.html#t:unsafe/0) May be safe or unsafe (i.e. it needs to be converted) ## [Functions](Phoenix.HTML.html#functions) [attributes\_escape(attrs)](Phoenix.HTML.html#attributes_escape/1) Escapes an enumerable of attributes, returning iodata. [css\_escape(value)](Phoenix.HTML.html#css_escape/1) Escapes a string for use as a CSS identifier. [html\_escape(safe)](Phoenix.HTML.html#html_escape/1) Escapes the HTML entities in the given term, returning safe iodata. [javascript\_escape(data)](Phoenix.HTML.html#javascript_escape/1) Escapes HTML content to be inserted into a JavaScript string. [raw(value)](Phoenix.HTML.html#raw/1) Marks the given content as raw. [safe\_to\_string(arg)](Phoenix.HTML.html#safe_to_string/1) Converts a safe result into a string. # [](Phoenix.HTML.html#types)Types [](Phoenix.HTML.html#t:safe/0) # safe() [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L92) ``` @type safe() :: {:safe, iodata()} ``` Guaranteed to be safe [](Phoenix.HTML.html#t:unsafe/0) # unsafe() [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L95) ``` @type unsafe() :: Phoenix.HTML.Safe.t() ``` May be safe or unsafe (i.e. it needs to be converted) # [](Phoenix.HTML.html#functions)Functions [](Phoenix.HTML.html#attributes_escape/1) # attributes\_escape(attrs) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L190) Escapes an enumerable of attributes, returning iodata. The attributes are rendered in the given order. Note if a map is given, the key ordering is not guaranteed. The keys and values can be of any shape, as long as they implement the [`Phoenix.HTML.Safe`](Phoenix.HTML.Safe.html) protocol. In addition, if the key is an atom, it will be "dasherized". In other words, `:phx_value_id` will be converted to `phx-value-id`. Furthermore, the following attributes provide behaviour: - `:aria`, `:data`, and `:phx` - they accept a keyword list as value. `data: [confirm: "are you sure?"]` is converted to `data-confirm="are you sure?"`. - `:class` - it accepts a list of classes as argument. Each element in the list is separated by space. `nil` and `false` elements are discarded. `class: ["foo", nil, "bar"]` then becomes `class="foo bar"`. - `:id` - it is validated raise if a number is given as ID, which is not allowed by the HTML spec and leads to unpredictable behaviour. ## [](Phoenix.HTML.html#attributes_escape/1-examples)Examples ``` iex> safe_to_string attributes_escape(title: "the title", id: "the id", selected: true) " title=\"the title\" id=\"the id\" selected" iex> safe_to_string attributes_escape(%{data: [confirm: "Are you sure?"]}) " data-confirm=\"Are you sure?\"" iex> safe_to_string attributes_escape(%{phx: [value: [foo: "bar"]]}) " phx-value-foo=\"bar\"" ``` [](Phoenix.HTML.html#css_escape/1) # css\_escape(value) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L354) ``` @spec css_escape(String.t()) :: String.t() ``` Escapes a string for use as a CSS identifier. ## [](Phoenix.HTML.html#css_escape/1-examples)Examples ``` iex> css_escape("hello world") "hello\\ world" iex> css_escape("-123") "-\\31 23" ``` [](Phoenix.HTML.html#html_escape/1) # html\_escape(safe) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L133) ``` @spec html_escape(unsafe()) :: safe() ``` Escapes the HTML entities in the given term, returning safe iodata. ``` iex> html_escape("<hello>") {:safe, [[[] | "&lt;"], "hello" | "&gt;"]} iex> html_escape(~c"<hello>") {:safe, ["&lt;", 104, 101, 108, 108, 111, "&gt;"]} iex> html_escape(1) {:safe, "1"} iex> html_escape({:safe, "<hello>"}) {:safe, "<hello>"} ``` [](Phoenix.HTML.html#javascript_escape/1) # javascript\_escape(data) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L309) ``` @spec javascript_escape(binary()) :: binary() ``` ``` @spec javascript_escape(safe()) :: safe() ``` Escapes HTML content to be inserted into a JavaScript string. This function is useful in JavaScript responses when there is a need to escape HTML rendered from other templates, like in the following: ``` $("#container").append("<%= javascript_escape(render("post.html", post: @post)) %>"); ``` It escapes quotes (double and single), double backslashes and others. [](Phoenix.HTML.html#raw/1) # raw(value) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L112) ``` @spec raw(iodata() | safe() | nil) :: safe() ``` Marks the given content as raw. This means any HTML code inside the given string won't be escaped. ``` iex> raw("<hello>") {:safe, "<hello>"} iex> raw({:safe, "<hello>"}) {:safe, "<hello>"} iex> raw(nil) {:safe, ""} ``` [](Phoenix.HTML.html#safe_to_string/1) # safe\_to\_string(arg) [](https://github.com/phoenixframework/phoenix_html/blob/v4.2.0/lib/phoenix_html.ex#L148) ``` @spec safe_to_string(safe()) :: String.t() ``` Converts a safe result into a string. Fails if the result is not safe. In such cases, you can invoke [`html_escape/1`](Phoenix.HTML.html#html_escape/1) or [`raw/1`](Phoenix.HTML.html#raw/1) accordingly before. You can combine [`html_escape/1`](Phoenix.HTML.html#html_escape/1) and [`safe_to_string/1`](Phoenix.HTML.html#safe_to_string/1) to convert a data structure to a escaped string: ``` data |> html_escape() |> safe_to_string() ``` [Hex Package](https://hex.pm/packages/phoenix_html/4.2.0) [Hex Preview](https://preview.hex.pm/preview/phoenix_html/4.2.0) Search HexDocs [Download ePub version](Phoenix.HTML.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/Phoenix.LiveDashboard.PageBuilder.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1 "View Source") Phoenix.LiveDashboard.PageBuilder behaviour (LiveDashboard v0.8.6) Page builder is the default mechanism for building custom dashboard pages. Each dashboard page is a LiveView with additional callbacks for customizing the menu appearance and the automatic refresh. A simple and straight-forward example of a custom page is the `Phoenix.LiveDashboard.EtsPage` that ships with the dashboard: ``` defmodule Phoenix.LiveDashboard.EtsPage do @moduledoc false use Phoenix.LiveDashboard.PageBuilder @impl true def menu_link(_, _) do {:ok, "ETS"} end @impl true def render(assigns) do ~H""" <.live_table id="ets-table" dom_id="ets-table" page={@page} title="ETS" row_fetcher={&fetch_ets/2} row_attrs={&row_attrs/1} rows_name="tables" > <:col field={:name} header="Name or module" /> <:col field={:protection} /> <:col field={:type} /> <:col field={:size} text_align="right" sortable={:desc} /> <:col field={:memory} text_align="right" sortable={:desc} :let={ets}> <%= format_words(ets[:memory]) %> </:col> <:col field={:owner} :let={ets} > <%= encode_pid(ets[:owner]) %> </:col> </.live_table> """ end defp fetch_ets(params, node) do %{search: search, sort_by: sort_by, sort_dir: sort_dir, limit: limit} = params # Here goes the code that goes through all ETS tables, searches # (if not nil), sorts, and limits them. # # It must return a tuple where the first element is list with # the current entries (up to limit) and an integer with the # total amount of entries. # ... end defp row_attrs(table) do [ {"phx-click", "show_info"}, {"phx-value-info", encode_ets(table[:id])}, {"phx-page-loading", true} ] end end ``` Once a page is defined, it must be declared in your `live_dashboard` route as follows: ``` live_dashboard "/dashboard", additional_pages: [ route_name: MyAppWeb.MyCustomPage ] ``` Or alternatively: ``` live_dashboard "/dashboard", additional_pages: [ route_name: {MyAppWeb.MyCustomPage, some_option: ...} ] ``` The second argument of the tuple will be given to the [`init/1`](Phoenix.LiveDashboard.PageBuilder.html#c:init/1) callback. If not tuple is given, [`init/1`](Phoenix.LiveDashboard.PageBuilder.html#c:init/1) will receive an empty list. ## [](Phoenix.LiveDashboard.PageBuilder.html#module-options-for-the-use-macro)Options for the use macro The following options can be given when using the `PageBuilder` module: - `refresher?` - Boolean to enable or disable the automatic refresh in the page. ## [](Phoenix.LiveDashboard.PageBuilder.html#module-components)Components A page can return any valid HEEx template in the `render/1` callback, and it can use the components listed with this page too. We currently support [`card/1`](Phoenix.LiveDashboard.PageBuilder.html#card/1), [`fields_card/1`](Phoenix.LiveDashboard.PageBuilder.html#fields_card/1), [`row/1`](Phoenix.LiveDashboard.PageBuilder.html#row/1), [`shared_usage_card/1`](Phoenix.LiveDashboard.PageBuilder.html#shared_usage_card/1), and [`usage_card/1`](Phoenix.LiveDashboard.PageBuilder.html#usage_card/1); and the live components [`live_layered_graph/1`](Phoenix.LiveDashboard.PageBuilder.html#live_layered_graph/1), [`live_nav_bar/1`](Phoenix.LiveDashboard.PageBuilder.html#live_nav_bar/1), and [`live_table/1`](Phoenix.LiveDashboard.PageBuilder.html#live_table/1). ## [](Phoenix.LiveDashboard.PageBuilder.html#module-helpers)Helpers Some helpers are available for page building. The supported helpers are: [`live_dashboard_path/2`](Phoenix.LiveDashboard.PageBuilder.html#live_dashboard_path/2), [`live_dashboard_path/3`](Phoenix.LiveDashboard.PageBuilder.html#live_dashboard_path/3), [`encode_app/1`](Phoenix.LiveDashboard.PageBuilder.html#encode_app/1), [`encode_ets/1`](Phoenix.LiveDashboard.PageBuilder.html#encode_ets/1), [`encode_pid/1`](Phoenix.LiveDashboard.PageBuilder.html#encode_pid/1), [`encode_port/1`](Phoenix.LiveDashboard.PageBuilder.html#encode_port/1), and [`encode_socket/1`](Phoenix.LiveDashboard.PageBuilder.html#encode_socket/1). ## [](Phoenix.LiveDashboard.PageBuilder.html#module-custom-hooks)Custom Hooks If your page needs to register custom hooks, you can use the [`register_after_opening_head_tag/2`](Phoenix.LiveDashboard.PageBuilder.html#register_after_opening_head_tag/2) function. Because the hooks need to be available on the dead render in the layout, before the LiveView's LiveSocket is configured, your need to do this inside an `on_mount` hook: ``` defmodule MyAppWeb.MyLiveDashboardHooks do import Phoenix.LiveView import Phoenix.Component alias Phoenix.LiveDashboard.PageBuilder def on_mount(:default, _params, _session, socket) do {:cont, PageBuilder.register_after_opening_head_tag(socket, &after_opening_head_tag/1)} end defp after_opening_head_tag(assigns) do ~H""" <script nonce={@csp_nonces[:script]}> window.LiveDashboard.registerCustomHooks({ MyHook: { mounted() { // do something } } }) </script> """ end end defmodule MyAppWeb.MyCustomPage do ... end ``` And then add it to the list of `on_mount` hooks in the `live_dashboard` router configuration: ``` live_dashboard "/dashboard", additional_pages: [ route_name: MyAppWeb.MyCustomPage ], on_mount: [ MyAppWeb.MyLiveDashboardHooks ] ``` The LiveDashboard provides a function `window.LiveDashboard.registerCustomHooks({ ... })` that you can call with an object of hook declarations. Note that in order to use external libraries, you will either need to include them from a CDN, or bundle them yourself and include them from your app's static paths. #### A note on CSPs and libraries Phoenix LiveDashboard supports CSP nonces for its own assets, configurable using the [`Phoenix.LiveDashboard.Router.live_dashboard/2`](Phoenix.LiveDashboard.Router.html#live_dashboard/2) macro by setting the `:csp_nonce_assign_key` option. If you are building a library, ensure that you render those CSP nonces on any scripts, styles or images of your page. The nonces are passed to your custom page under the `:csp_nonces` assign and also available in the `after_opening_head_tag` component. You should use those when including scripts or styles like this: ``` <script nonce={@csp_nonces.script}>...</script> <script nonce={@csp_nonces.script} src="..."></script> <style nonce={@csp_nonces.style}>...</style> <link rel="stylesheet" href="..." nonce={@csp_nonces.style}> ``` This ensures that your custom page can be used when a CSP is in place using the mechanism supported by Phoenix LiveDashboard. If your custom page needs a different CSP policy, for example due to inline styles set by scripts, please consider documenting these requirements. # [](Phoenix.LiveDashboard.PageBuilder.html#summary)Summary ## [Components](Phoenix.LiveDashboard.PageBuilder.html#components) [card(assigns)](Phoenix.LiveDashboard.PageBuilder.html#card/1) Card component. [card\_title(assigns)](Phoenix.LiveDashboard.PageBuilder.html#card_title/1) Card title component. [fields\_card(assigns)](Phoenix.LiveDashboard.PageBuilder.html#fields_card/1) Fields card component. [hint(assigns)](Phoenix.LiveDashboard.PageBuilder.html#hint/1) Hint pop-up text component [label\_value\_list(assigns)](Phoenix.LiveDashboard.PageBuilder.html#label_value_list/1) List of label value. [live\_layered\_graph(assigns)](Phoenix.LiveDashboard.PageBuilder.html#live_layered_graph/1) A component for drawing layered graphs. [live\_nav\_bar(assigns)](Phoenix.LiveDashboard.PageBuilder.html#live_nav_bar/1) Nav bar live component. [live\_table(assigns)](Phoenix.LiveDashboard.PageBuilder.html#live_table/1) Table live component. [row(assigns)](Phoenix.LiveDashboard.PageBuilder.html#row/1) Row component. [shared\_usage\_card(assigns)](Phoenix.LiveDashboard.PageBuilder.html#shared_usage_card/1) Shared usage card component. [usage\_card(assigns)](Phoenix.LiveDashboard.PageBuilder.html#usage_card/1) Usage card component. ## [Types](Phoenix.LiveDashboard.PageBuilder.html#types) [capabilities()](Phoenix.LiveDashboard.PageBuilder.html#t:capabilities/0) [requirements()](Phoenix.LiveDashboard.PageBuilder.html#t:requirements/0) [session()](Phoenix.LiveDashboard.PageBuilder.html#t:session/0) [unsigned\_params()](Phoenix.LiveDashboard.PageBuilder.html#t:unsigned_params/0) ## [Callbacks](Phoenix.LiveDashboard.PageBuilder.html#callbacks) [handle\_event(event, unsigned\_params, socket)](Phoenix.LiveDashboard.PageBuilder.html#c:handle_event/3) Callback invoked when an event is called. [handle\_info(msg, socket)](Phoenix.LiveDashboard.PageBuilder.html#c:handle_info/2) [handle\_params(unsigned\_params, uri, socket)](Phoenix.LiveDashboard.PageBuilder.html#c:handle_params/3) [handle\_refresh(socket)](Phoenix.LiveDashboard.PageBuilder.html#c:handle_refresh/1) Callback invoked when the automatic refresh is enabled. [init(term)](Phoenix.LiveDashboard.PageBuilder.html#c:init/1) Callback invoked when a page is declared in the router. [menu\_link(session, capabilities)](Phoenix.LiveDashboard.PageBuilder.html#c:menu_link/2) Callback invoked when a page is declared in the router. [mount(unsigned\_params, session, socket)](Phoenix.LiveDashboard.PageBuilder.html#c:mount/3) [render(assigns)](Phoenix.LiveDashboard.PageBuilder.html#c:render/1) ## [Functions](Phoenix.LiveDashboard.PageBuilder.html#functions) [encode\_app(app)](Phoenix.LiveDashboard.PageBuilder.html#encode_app/1) Encodes an application for URLs. [encode\_ets(ref)](Phoenix.LiveDashboard.PageBuilder.html#encode_ets/1) Encodes ETSs references for URLs. [encode\_pid(pid)](Phoenix.LiveDashboard.PageBuilder.html#encode_pid/1) Encodes PIDs for URLs. [encode\_port(port)](Phoenix.LiveDashboard.PageBuilder.html#encode_port/1) Encodes Port for URLs. [encode\_socket(ref)](Phoenix.LiveDashboard.PageBuilder.html#encode_socket/1) Encodes Sockets for URLs. [live\_dashboard\_path(socket, map)](Phoenix.LiveDashboard.PageBuilder.html#live_dashboard_path/2) Computes a router path to the current page. [live\_dashboard\_path(socket, map, extra)](Phoenix.LiveDashboard.PageBuilder.html#live_dashboard_path/3) Computes a router path to the current page with merged params. [register\_after\_opening\_head\_tag(socket, component)](Phoenix.LiveDashboard.PageBuilder.html#register_after_opening_head_tag/2) Registers a component to be rendered after the opening head tag in the layout. [register\_before\_closing\_head\_tag(socket, component)](Phoenix.LiveDashboard.PageBuilder.html#register_before_closing_head_tag/2) Registers a component to be rendered before the closing head tag in the layout. # [](Phoenix.LiveDashboard.PageBuilder.html#components)Components [](Phoenix.LiveDashboard.PageBuilder.html#card/1) # card(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L474) ``` @spec card(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Card component. You can see it in use the Home and OS Data pages. ## [](Phoenix.LiveDashboard.PageBuilder.html#card/1-attributes)Attributes - `title` (`:string`) - The title above the card. Defaults to `nil`. - `hint` (`:string`) - A textual hint to show close to the title. Defaults to `nil`. - `inner_title` (`:string`) - The title inside the card. Defaults to `nil`. - `inner_hint` (`:string`) - A textual hint to show close to the inner title. Defaults to `nil`. - `dom_id` (`:string`) - id attribute for the HTML the main tag. Defaults to `nil`. ## [](Phoenix.LiveDashboard.PageBuilder.html#card/1-slots)Slots - `inner_block` (required) - The value that the card will show. [](Phoenix.LiveDashboard.PageBuilder.html#card_title/1) # card\_title(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L451) ``` @spec card_title(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Card title component. ## [](Phoenix.LiveDashboard.PageBuilder.html#card_title/1-attributes)Attributes - `title` (`:string`) - The title above the card. Defaults to `nil`. - `hint` (`:string`) - A textual hint to show close to the title. Defaults to `nil`. [](Phoenix.LiveDashboard.PageBuilder.html#fields_card/1) # fields\_card(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L504) Fields card component. You can see it in use the Home page in the Environment section. ## [](Phoenix.LiveDashboard.PageBuilder.html#fields_card/1-attributes)Attributes - `fields` (`:list`) (required) - A list of key-value elements that will be shown inside the card. - `title` (`:string`) - The title above the card. Defaults to `nil`. - `hint` (`:string`) - A textual hint to show close to the title. Defaults to `nil`. - `inner_title` (`:string`) - The title inside the card. Defaults to `nil`. - `inner_hint` (`:string`) - A textual hint to show close to the inner title. Defaults to `nil`. [](Phoenix.LiveDashboard.PageBuilder.html#hint/1) # hint(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L429) ``` @spec hint(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Hint pop-up text component ## [](Phoenix.LiveDashboard.PageBuilder.html#hint/1-attributes)Attributes - `text` (`:string`) (required) - Text to show in the hint. [](Phoenix.LiveDashboard.PageBuilder.html#label_value_list/1) # label\_value\_list(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L857) List of label value. You can see it in use in the modal in Ports or Processes page. ## [](Phoenix.LiveDashboard.PageBuilder.html#label_value_list/1-slots)Slots - `elem` (required) - Value for each element of the list. Accepts attributes: - `label` (`:string`) (required) - Label for the elem. [](Phoenix.LiveDashboard.PageBuilder.html#live_layered_graph/1) # live\_layered\_graph(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L841) ``` @spec live_layered_graph(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` A component for drawing layered graphs. This is useful to represent pipelines like we have on [BroadwayDashboard](../broadway_dashboard.html) where each layer points to nodes of the layer below. It draws the layers from top to bottom. The calculation of layers and positions is done automatically based on options. ## [](Phoenix.LiveDashboard.PageBuilder.html#live_layered_graph/1-attributes)Attributes - `id` (`:any`) (required) - Because is a stateful [`Phoenix.LiveComponent`](../phoenix_live_view/1.0.0/Phoenix.LiveComponent.html) an unique id is needed. - `title` (`:string`) - The title of the component. Defaults to `nil`. - `hint` (`:string`) - A textual hint to show close to the title. Defaults to `nil`. - `layers` (`:list`) (required) - A graph of layers with nodes. They represent our graph structure (see example). Each layer is a list of nodes, where each node has the following fields: - `:id` - The ID of the given node. - `:children` - The IDs of children nodes. - `:data` - A string or a map. If it's a map, the required fields are `detail` and `label`. - `show_grid?` (`:boolean`) - Enable or disable the display of a grid. This is useful for development. Defaults to `false`. - `y_label_offset` (`:integer`) - The "y" offset of label position relative to the center of its circle. Defaults to `5`. - `y_detail_offset` (`:integer`) - The "y" offset of detail position relative to the center of its circle. Defaults to `18`. - `background` (`:any`) - A function that calculates the background for a node based on it's data. Default: `fn _node_data -> "gray" end`." - `format_label` (`:any`) - A function that formats the label. Defaults to a function that returns the label or data if data is binary. - `format_detail` (`:any`) - A function that formats the detail field. This is only going to be called if data is a map. Default: `fn node_data -> node_data.detail end`. ## [](Phoenix.LiveDashboard.PageBuilder.html#live_layered_graph/1-examples)Examples ``` iex> layers = [ ...> [ ...> %{ ...> id: "a1", ...> data: "a1", ...> children: ["b1"] ...> } ...> ], ...> [ ...> %{ ...> id: "b1" ...> data: %{ ...> detail: 0, ...> label: "b1" ...> }, ...> children: [] ...> } ...> ] ...> ] ``` [](Phoenix.LiveDashboard.PageBuilder.html#live_nav_bar/1) # live\_nav\_bar(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L416) ``` @spec live_nav_bar(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Nav bar live component. You can see it in use the Metrics and Ecto info pages. ## [](Phoenix.LiveDashboard.PageBuilder.html#live_nav_bar/1-attributes)Attributes - `id` (`:any`) (required) - Because is a stateful [`Phoenix.LiveComponent`](../phoenix_live_view/1.0.0/Phoenix.LiveComponent.html) an unique id is needed. - `page` ([`Phoenix.LiveDashboard.PageBuilder`](Phoenix.LiveDashboard.PageBuilder.html)) (required) - Dashboard page. - `nav_param` (`:string`) - An atom that configures the navigation parameter. It is useful when two nav bars are present in the same page. Defaults to `"nav"`. - `extra_params` (`:list`) - A list of strings representing the parameters that should stay when a tab is clicked. By default the nav ignores all params, except the current node if any. Defaults to `[]`. - `style` (`:atom`) - Style for the nav bar.Must be one of `:pills`, or `:bar`. ## [](Phoenix.LiveDashboard.PageBuilder.html#live_nav_bar/1-slots)Slots - `item` (required) - HTML to be rendered when the tab is selected. Accepts attributes: - `name` (`:string`) (required) - Value used in the URL when the tab is selected. - `label` (`:string`) - Title of the tab. If it is not present, it will be calculated from `name`. - `method` (`:string`) - Method used to update.Must be one of `"patch"`, `"navigate"`, `"href"`, or `"redirect"`. [](Phoenix.LiveDashboard.PageBuilder.html#live_table/1) # live\_table(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L371) ``` @spec live_table(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Table live component. You can see it in use the applications, processes, sockets pages and many others. ## [](Phoenix.LiveDashboard.PageBuilder.html#live_table/1-attributes)Attributes - `id` (`:any`) (required) - Because is a stateful [`Phoenix.LiveComponent`](../phoenix_live_view/1.0.0/Phoenix.LiveComponent.html) an unique id is needed. - `page` ([`Phoenix.LiveDashboard.PageBuilder`](Phoenix.LiveDashboard.PageBuilder.html)) (required) - Dashboard page. - `row_fetcher` (`:any`) (required) - A function which receives the params and the node and returns a tuple with the rows and the total number: `(params(), node() -> {list(), integer() | binary()})`. Optionally, if the function needs to keep a state, it can be defined as a tuple where the first element is a function and the second is the initial state. In this case, the function will receive the state as third argument and must return a tuple with the rows, the total number, and the new state for the following call: `{(params(), node(), term() -> {list(), integer() | binary(), term()}), term()}` - `rows_name` (`:string`) - A string to name the representation of the rows. Default is calculated from the current page. - `row_attrs` (`:any`) - A list with the HTML attributes for the table row. It can be also a function that receive the row as argument and returns a list of 2 element tuple with HTML attribute name and value. Defaults to `nil`. - `default_sort_by` (`:any`) - The default column to sort by to. Defaults to the first sortable column. Defaults to `nil`. - `title` (`:string`) (required) - The title of the table. - `limit` (`:any`) - May be set to `false` to disable the `limit`. Defaults to `[50, 100, 500, 1000, 5000]`. - `search` (`:boolean`) - A boolean indicating if the search functionality is enabled. Defaults to `true`. - `hint` (`:string`) - A textual hint to show close to the title. Defaults to `nil`. - `dom_id` (`:string`) - id attribute for the HTML the main tag. Defaults to `nil`. ## [](Phoenix.LiveDashboard.PageBuilder.html#live_table/1-slots)Slots - `col` (required) - Columns for the table. Accepts attributes: - `field` (`:atom`) (required) - Identifier for the column. - `sortable` (`:atom`) - When set, the column header is clickable and it fetches again rows with the new order. Required for at least one column. Must be one of `:asc`, or `:desc`. - `header` (`:string`) - Label to show in the current column. Default value is calculated from `:field`. - `text_align` (`:string`) - Text align for text in the column. Default: `nil`.Must be one of `"left"`, `"center"`, `"right"`, or `"justify"`. [](Phoenix.LiveDashboard.PageBuilder.html#row/1) # row(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L541) ``` @spec row(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Row component. You can see it in use the Home page and OS Data pages. ## [](Phoenix.LiveDashboard.PageBuilder.html#row/1-slots)Slots - `col` (required) - A list of components. It can receive up to 3 components. Each element will be one column. [](Phoenix.LiveDashboard.PageBuilder.html#shared_usage_card/1) # shared\_usage\_card(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L694) ``` @spec shared_usage_card(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Shared usage card component. You can see it in use the Home page and OS Data pages. ## [](Phoenix.LiveDashboard.PageBuilder.html#shared_usage_card/1-attributes)Attributes - `usages` (`:list`) (required) - A list of [`Map`](../elixir/Map.html) with the following keys: - `:data` - A list of tuples with 4 elements with the following data: `{usage_name, usage_percent, color, hint}` - `:dom_id` - Required. Usage identifier. - `:title`- Bar title. - `total_data` (`:any`) (required) - A list of tuples with 4 elements with following data: `{usage_name, usage_value, color, hint}`. - `total_legend` (`:string`) (required) - The legent of the total usage. - `total_usage` (`:string`) (required) - The value of the total usage. - `dom_id` (`:string`) - id attribute for the HTML the main tag. Defaults to `nil`. - `csp_nonces` (`:any`) (required) - A copy of CSP nonces (`@csp_nonces`) used to render the page safely. - `title` (`:string`) - The title above the card. Defaults to `nil`. - `hint` (`:string`) - A textual hint to show close to the title. Defaults to `nil`. - `inner_title` (`:string`) - The title inside the card. Defaults to `nil`. - `inner_hint` (`:string`) - A textual hint to show close to the inner title. Defaults to `nil`. - `total_formatter` (`:any`) - A function that format the `total_usage`. Default: `&("#{&1} %")`. Defaults to `nil`. [](Phoenix.LiveDashboard.PageBuilder.html#usage_card/1) # usage\_card(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L593) ``` @spec usage_card(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Usage card component. You can see it in use the Home page and OS Data pages. ## [](Phoenix.LiveDashboard.PageBuilder.html#usage_card/1-attributes)Attributes - `title` (`:string`) - The title above the card. Defaults to `nil`. - `hint` (`:string`) - A textual hint to show close to the title. Defaults to `nil`. - `dom_id` (`:string`) (required) - A unique identifier for all usages in this card. - `csp_nonces` (`:any`) (required) - A copy of CSP nonces (`@csp_nonces`) used to render the page safely. ## [](Phoenix.LiveDashboard.PageBuilder.html#usage_card/1-slots)Slots - `usage` (required) - List of usages to show. Accepts attributes: - `current` (`:integer`) (required) - The current value of the usage. - `limit` (`:integer`) (required) - The max value of usage. - `dom_id` (`:string`) (required) - An unique identifier for the usage that will be concatenated to `dom_id`. - `percent` (`:string`) - The used percent of the usage. - `title` (`:string`) - The title of the usage. - `hint` (`:string`) - A textual hint to show close to the usage title. # [](Phoenix.LiveDashboard.PageBuilder.html#types)Types [](Phoenix.LiveDashboard.PageBuilder.html#t:capabilities/0) # capabilities() [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L201) ``` @type capabilities() :: %{ applications: [atom()], modules: [atom()], processes: [atom()], dashboard_running?: boolean(), system_info: nil | binary() } ``` [](Phoenix.LiveDashboard.PageBuilder.html#t:requirements/0) # requirements() [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L199) ``` @type requirements() :: [{:application | :process | :module, atom()}] ``` [](Phoenix.LiveDashboard.PageBuilder.html#t:session/0) # session() [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L198) ``` @type session() :: map() ``` [](Phoenix.LiveDashboard.PageBuilder.html#t:unsigned_params/0) # unsigned\_params() [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L200) ``` @type unsigned_params() :: map() ``` # [](Phoenix.LiveDashboard.PageBuilder.html#callbacks)Callbacks [](Phoenix.LiveDashboard.PageBuilder.html#c:handle_event/3) # handle\_event(event, unsigned\_params, socket) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L280)(optional) ``` @callback handle_event(event :: binary(), unsigned_params(), socket :: Socket.t()) :: {:noreply, Socket.t()} | {:reply, map(), Socket.t()} ``` Callback invoked when an event is called. Note that `show_info` event is handled automatically by [`Phoenix.LiveDashboard.PageBuilder`](Phoenix.LiveDashboard.PageBuilder.html), but the `info` parameter (`phx-value-info`) needs to be encoded with one of the `encode_*` helper functions. For more details, see [`Phoenix.LiveView bindings`](../phoenix_live_view/Phoenix.LiveView.html#module-bindings) [](Phoenix.LiveDashboard.PageBuilder.html#c:handle_info/2) # handle\_info(msg, socket) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L283)(optional) ``` @callback handle_info(msg :: term(), socket :: Socket.t()) :: {:noreply, Socket.t()} ``` [](Phoenix.LiveDashboard.PageBuilder.html#c:handle_params/3) # handle\_params(unsigned\_params, uri, socket) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L267)(optional) ``` @callback handle_params(unsigned_params(), uri :: String.t(), socket :: Socket.t()) :: {:noreply, Socket.t()} ``` [](Phoenix.LiveDashboard.PageBuilder.html#c:handle_refresh/1) # handle\_refresh(socket) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L289)(optional) ``` @callback handle_refresh(socket :: Socket.t()) :: {:noreply, Socket.t()} ``` Callback invoked when the automatic refresh is enabled. [](Phoenix.LiveDashboard.PageBuilder.html#c:init/1) # init(term) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L237) ``` @callback init(term()) :: {:ok, session()} | {:ok, session(), requirements()} ``` Callback invoked when a page is declared in the router. It receives the router options and it must return the tuple `{:ok, session, requirements}`. The page session will be serialized to the client and received on `mount`. The requirements is an optional keyword to detect the state of the node. The result of this detection will be passed as second argument in the [`menu_link/2`](Phoenix.LiveDashboard.PageBuilder.html#c:menu_link/2) callback. The possible values are: - `:applications` list of applications that are running or not. - `:modules` list of modules that are loaded or not. - `:pids` list of processes that alive or not. [](Phoenix.LiveDashboard.PageBuilder.html#c:menu_link/2) # menu\_link(session, capabilities) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L256) ``` @callback menu_link(session(), capabilities()) :: {:ok, String.t()} | {:disabled, String.t()} | {:disabled, String.t(), String.t()} | :skip ``` Callback invoked when a page is declared in the router. It receives the session returned by the [`init/1`](Phoenix.LiveDashboard.PageBuilder.html#c:init/1) callback and the capabilities of the current node. The possible return values are: - `{:ok, text}` when the link should be enable and text to be shown. - `{:disabled, text}` when the link should be disable and text to be shown. - `{:disabled, text, more_info_url}` similar to the previous one but it also includes a link to provide more information to the user. - `:skip` when the link should not be shown at all. [](Phoenix.LiveDashboard.PageBuilder.html#c:mount/3) # mount(unsigned\_params, session, socket) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L262)(optional) ``` @callback mount(unsigned_params(), session(), socket :: Socket.t()) :: {:ok, Socket.t()} | {:ok, Socket.t(), keyword()} ``` [](Phoenix.LiveDashboard.PageBuilder.html#c:render/1) # render(assigns) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L265) ``` @callback render(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` # [](Phoenix.LiveDashboard.PageBuilder.html#functions)Functions [](Phoenix.LiveDashboard.PageBuilder.html#encode_app/1) # encode\_app(app) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1031) ``` @spec encode_app(atom()) :: binary() ``` Encodes an application for URLs. ## [](Phoenix.LiveDashboard.PageBuilder.html#encode_app/1-example)Example This function can be used to encode an application for an event value: ``` <button phx-click="show-info" phx-value-info=<%= encode_app(@my_app) %>/> ``` [](Phoenix.LiveDashboard.PageBuilder.html#encode_ets/1) # encode\_ets(ref) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L985) ``` @spec encode_ets(reference()) :: binary() ``` Encodes ETSs references for URLs. ## [](Phoenix.LiveDashboard.PageBuilder.html#encode_ets/1-example)Example This function can be used to encode an ETS reference for an event value: ``` <button phx-click="show-info" phx-value-info=<%= encode_ets(@reference) %>/> ``` [](Phoenix.LiveDashboard.PageBuilder.html#encode_pid/1) # encode\_pid(pid) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1000) ``` @spec encode_pid(pid()) :: binary() ``` Encodes PIDs for URLs. ## [](Phoenix.LiveDashboard.PageBuilder.html#encode_pid/1-example)Example This function can be used to encode a PID for an event value: ``` <button phx-click="show-info" phx-value-info=<%= encode_pid(@pid) %>/> ``` [](Phoenix.LiveDashboard.PageBuilder.html#encode_port/1) # encode\_port(port) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1014) ``` @spec encode_port(port()) :: binary() ``` Encodes Port for URLs. ## [](Phoenix.LiveDashboard.PageBuilder.html#encode_port/1-example)Example This function can be used to encode a Port for an event value: ``` <button phx-click="show-info" phx-value-info=<%= encode_port(@port) %>/> ``` [](Phoenix.LiveDashboard.PageBuilder.html#encode_socket/1) # encode\_socket(ref) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L966) ``` @spec encode_socket(port() | binary()) :: binary() ``` Encodes Sockets for URLs. ## [](Phoenix.LiveDashboard.PageBuilder.html#encode_socket/1-example)Example This function can be used to encode `@socket` for an event value: ``` <button phx-click="show-info" phx-value-info=<%= encode_socket(@socket) %>/> ``` [](Phoenix.LiveDashboard.PageBuilder.html#live_dashboard_path/2) # live\_dashboard\_path(socket, map) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1039) ``` @spec live_dashboard_path( Socket.t(), page :: %Phoenix.LiveDashboard.PageBuilder{ allow_destructive_actions: term(), info: term(), module: term(), node: term(), params: term(), route: term(), tick: term() } ) :: binary() ``` Computes a router path to the current page. [](Phoenix.LiveDashboard.PageBuilder.html#live_dashboard_path/3) # live\_dashboard\_path(socket, map, extra) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1047) ``` @spec live_dashboard_path( Socket.t(), page :: %Phoenix.LiveDashboard.PageBuilder{ allow_destructive_actions: term(), info: term(), module: term(), node: term(), params: term(), route: term(), tick: term() }, map() | Keyword.t() ) :: binary() ``` Computes a router path to the current page with merged params. [](Phoenix.LiveDashboard.PageBuilder.html#register_after_opening_head_tag/2) # register\_after\_opening\_head\_tag(socket, component) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1055) Registers a component to be rendered after the opening head tag in the layout. [](Phoenix.LiveDashboard.PageBuilder.html#register_before_closing_head_tag/2) # register\_before\_closing\_head\_tag(socket, component) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/page_builder.ex#L1062) Registers a component to be rendered before the closing head tag in the layout. [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/Phoenix.LiveDashboard.RequestLogger.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/request_logger.ex#L1 "View Source") Phoenix.LiveDashboard.RequestLogger (LiveDashboard v0.8.6) A plug that enables request logging. See [our Request Logger guides](request_logger.html) for more information. [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/Phoenix.LiveDashboard.Router.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/router.ex#L1 "View Source") Phoenix.LiveDashboard.Router (LiveDashboard v0.8.6) Provides LiveView routing for LiveDashboard. # [](Phoenix.LiveDashboard.Router.html#summary)Summary ## [Functions](Phoenix.LiveDashboard.Router.html#functions) [live\_dashboard(path, opts \\\\ \[\])](Phoenix.LiveDashboard.Router.html#live_dashboard/2) Defines a LiveDashboard route. # [](Phoenix.LiveDashboard.Router.html#functions)Functions [](Phoenix.LiveDashboard.Router.html#live_dashboard/2) # live\_dashboard(path, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard/router.ex#L89)(macro) Defines a LiveDashboard route. It expects the `path` the dashboard will be mounted at and a set of options. You can then link to the route directly: ``` <a href={~p"/dashboard"}>Dashboard</a> ``` ## [](Phoenix.LiveDashboard.Router.html#live_dashboard/2-options)Options - `:live_socket_path` - Configures the socket path. it must match the `socket "/live", Phoenix.LiveView.Socket` in your endpoint. - `:csp_nonce_assign_key` - an assign key to find the CSP nonce value used for assets. Supports either `atom()` or a map of type `%{optional(:img) => atom(), optional(:script) => atom(), optional(:style) => atom()}` - `:ecto_repos` - the repositories to show database information. Currently only PostgreSQL, MySQL, and SQLite databases are supported. If you don't specify but your app is running Ecto, we will try to auto-discover the available repositories. You can disable this behavior by setting `[]` to this option. - `:env_keys` - Configures environment variables to display. It is defined as a list of string keys. If not set, the environment information will not be displayed - `:home_app` - A tuple with the app name and version to show on the home page. Defaults to `{"Dashboard", :phoenix_live_dashboard}` - `:metrics` - Configures the module to retrieve metrics from. It can be a `module` or a `{module, function}`. If nothing is given, the metrics functionality will be disabled. If `false` is passed, then the menu item won't be visible. - `:metrics_history` - Configures a callback for retrieving metric history. It must be an "MFA" tuple of `{Module, :function, arguments}` such as metrics\_history: {MyStorage, :metrics\_history, \[]} If not set, metrics will start out empty/blank and only display data that occurs while the browser page is open. - `:on_mount` - Declares a custom list of [`Phoenix.LiveView.on_mount/1`](../phoenix_live_view/1.0.0/Phoenix.LiveView.html#on_mount/1) callbacks to add to the dashboard's [`Phoenix.LiveView.Router.live_session/3`](../phoenix_live_view/1.0.0/Phoenix.LiveView.Router.html#live_session/3). A single value may also be declared. - `:request_logger` - By default the Request Logger page is enabled. Passing `false` will disable this page. - `:request_logger_cookie_domain` - Configures the domain the request\_logger cookie will be written to. It can be a string or `:parent` atom. When a string is given, it will directly set cookie domain to the given value. When `:parent` is given, it will take the parent domain from current endpoint host (if host is "www.acme.com" the cookie will be scoped on "acme.com"). When not set, the cookie will be scoped to current domain. - `:allow_destructive_actions` - When true, allow destructive actions directly from the UI. Defaults to `false`. The following destructive actions are available in the dashboard: - "Kill process" - a "Kill process" button on the process modal Note that custom pages given to "Additional pages" may support their own destructive actions. - `:additional_pages` - A keyword list of additional pages ## [](Phoenix.LiveDashboard.Router.html#live_dashboard/2-examples)Examples ``` defmodule MyAppWeb.Router do use Phoenix.Router import Phoenix.LiveDashboard.Router scope "/", MyAppWeb do pipe_through [:browser] live_dashboard "/dashboard", metrics: {MyAppWeb.Telemetry, :metrics}, env_keys: ["APP_USER", "VERSION"], metrics_history: {MyStorage, :metrics_history, []}, request_logger_cookie_domain: ".acme.com" end end ``` [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/Phoenix.LiveDashboard.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard.ex#L1 "View Source") Phoenix.LiveDashboard (LiveDashboard v0.8.6) LiveDashboard provides real-time performance monitoring and debugging tools for Phoenix developers. It provides the following modules: - Home - See general information about the system - OS Data - See general information about OS, such as CPU, Memory and Disk usage - Metrics - See how your application performs under different conditions by visualizing [`:telemetry`](../telemetry.html) events with real-time charts - Request logging - See everything that was logged for certain requests - Applications - See, filter, and search applications in the current node and view their processes in a supervision tree - Processes - See, filter, and search processes in the current node - Ports - See, filter, and search ports (responsible for I/O) in the current node - Sockets - See, filter, and search sockets (responsible for tcp/udp) in the current node - ETS - See, filter, and search ETS tables (in-memory storage) in the current node - Ecto Stats - Shows index, table, and general usage about the underlying Ecto Repo storage The dashboard also works across nodes. If your nodes are connected via Distributed Erlang, then you can access information from node B while accessing the dashboard on node A. ![screenshot](https://github.com/phoenixframework/phoenix_live_dashboard/raw/main/screenshot.png) ## [](Phoenix.LiveDashboard.html#module-installation)Installation The LiveDashboard is built on top of LiveView. Both LiveView and LiveDashboard ship by default as part of Phoenix. But if you skipped them, you can LiveDashboard in three steps: 1. Add the `phoenix_live_dashboard` dependency 2. Configure LiveView 3. Add dashboard access ### [](Phoenix.LiveDashboard.html#module-1-add-the-phoenix_live_dashboard-dependency)1. Add the `phoenix_live_dashboard` dependency Add the following to your `mix.exs` and run [`mix deps.get`](../mix/Mix.Tasks.Deps.Get.html): ``` def deps do [ {:phoenix_live_dashboard, "~> 0.7"} ] end ``` ### [](Phoenix.LiveDashboard.html#module-2-configure-liveview)2. Configure LiveView First, update your endpoint's configuration to include a signing salt: ``` # config/config.exs config :my_app, MyAppWeb.Endpoint, live_view: [signing_salt: "SECRET_SALT"] ``` Then add the [`Phoenix.LiveView.Socket`](../phoenix_live_view/1.0.0/Phoenix.LiveView.Socket.html) declaration to your endpoint: ``` socket "/live", Phoenix.LiveView.Socket ``` And you are good to go! ### [](Phoenix.LiveDashboard.html#module-3-add-dashboard-access-for-development-only-usage)3. Add dashboard access for development-only usage Once installed, update your router's configuration to forward requests to a LiveDashboard with a unique `name` of your choosing: ``` # lib/my_app_web/router.ex use MyAppWeb, :router import Phoenix.LiveDashboard.Router ... if Mix.env() == :dev do scope "/" do pipe_through :browser live_dashboard "/dashboard" end end ``` This is all. Run [`mix phx.server`](../phoenix/1.7.17/Mix.Tasks.Phx.Server.html) and access the "/dashboard" to configure the necessary modules. ### [](Phoenix.LiveDashboard.html#module-extra-add-dashboard-access-on-all-environments-including-production)Extra: Add dashboard access on all environments (including production) If you want to use the LiveDashboard in production, you should put authentication in front of it. For example, if you use [`mix phx.gen.auth`](../phoenix/1.7.17/Mix.Tasks.Phx.Gen.Auth.html) to generate an Admin resource, you could use the following code: ``` # lib/my_app_web/router.ex use MyAppWeb, :router import Phoenix.LiveDashboard.Router ... pipeline :admins_only do plug :fetch_current_admin plug :require_authenticated_admin end scope "/" do pipe_through [:browser, :admins_only] live_dashboard "/dashboard" end ``` If you'd rather have some quick and dirty HTTP Authentication, the following code can be used as a starting point: ``` # lib/my_app_web/router.ex use MyAppWeb, :router import Phoenix.LiveDashboard.Router ... pipeline :admins_only do plug :admin_basic_auth end scope "/" do pipe_through [:browser, :admins_only] live_dashboard "/dashboard" end defp admin_basic_auth(conn, _opts) do username = System.fetch_env!("AUTH_USERNAME") password = System.fetch_env!("AUTH_PASSWORD") Plug.BasicAuth.basic_auth(conn, username: username, password: password) end ``` If you are running your application behind a proxy or a webserver, you also have to make sure they are configured for allowing WebSocket upgrades. For example, [here is an article](https://web.archive.org/web/20171104012240/https://dennisreimann.de/articles/phoenix-nginx-config.html) on how to configure Nginx with Phoenix and WebSockets. # [](Phoenix.LiveDashboard.html#summary)Summary ## [Functions](Phoenix.LiveDashboard.html#functions) [extract\_datapoint\_for\_metric(metric, measurements, metadata, time \\\\ nil)](Phoenix.LiveDashboard.html#extract_datapoint_for_metric/4) Extracts a datapoint for the given metric. # [](Phoenix.LiveDashboard.html#functions)Functions [](Phoenix.LiveDashboard.html#extract_datapoint_for_metric/4) # extract\_datapoint\_for\_metric(metric, measurements, metadata, time \\\\ nil) [](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/lib/phoenix/live_dashboard.ex#L21) ``` @spec extract_datapoint_for_metric( Telemetry.Metric.t(), map(), map(), pos_integer() | nil ) :: %{label: binary(), measurement: number(), time: pos_integer()} | nil ``` Extracts a datapoint for the given metric. Receives a `Telemetry.Metric` as `metric`, alongside the `measurements` and `metadata` from the Telemetry event, and an optional `time` and returns an extracted datapoint or `nil` if the event is not part of the metric. Note that it is expected that the event name was already validated as part of the metric. [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/api-reference.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard "View Source") API Reference LiveDashboard v0.8.6 ## [](api-reference.html#modules)Modules [Phoenix.LiveDashboard](Phoenix.LiveDashboard.html) LiveDashboard provides real-time performance monitoring and debugging tools for Phoenix developers. It provides the following modules [Phoenix.LiveDashboard.PageBuilder](Phoenix.LiveDashboard.PageBuilder.html) Page builder is the default mechanism for building custom dashboard pages. [Phoenix.LiveDashboard.RequestLogger](Phoenix.LiveDashboard.RequestLogger.html) A plug that enables request logging. [Phoenix.LiveDashboard.Router](Phoenix.LiveDashboard.Router.html) Provides LiveView routing for LiveDashboard. [Next Page → Configuring Ecto repository stats](ecto_stats.html) [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/ecto_stats.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/guides/ecto_stats.md#L1 "View Source") Configuring Ecto repository stats This guide covers how to configure the LiveDashboard to show stats from your underlying database. At the moment, these stats can only be shown for Ecto repositories running on [`Ecto.Adapters.Postgres`](../ecto_sql/3.12.1/Ecto.Adapters.Postgres.html) or [`Ecto.Adapters.MyXQL`](../ecto_sql/3.12.1/Ecto.Adapters.MyXQL.html). ## [](ecto_stats.html#installing-ecto-stats)Installing Ecto Stats ### [](ecto_stats.html#postgresql)PostgreSQL To enable the "Ecto Stats" functionality for PostgreSQL in your dashboard, you will need to do the three steps below: 1. Add the [`ecto_psql_extras`](../ecto_psql_extras.html) dependency 2. (optional) Configure the dashboard 3. (optional) Install custom PostgreSQL extensions #### Add the `ecto_psql_extras` dependency In your `mix.exs`, add the following to your `deps`: ``` {:ecto_psql_extras, "~> 0.6"}, ``` ### [](ecto_stats.html#mysql-mariadb)MySQL/MariaDB To enable the "Ecto Stats" functionality for MySQL or MariaDB in your dashboard, you will need to do the three steps below: 1. Add the [`ecto_mysql_extras`](../ecto_mysql_extras.html) dependency 2. (optional) Configure the dashboard 3. (optional) MySQL/MariaDB configuration #### Add the `ecto_mysql_extras` dependency In your `mix.exs`, add the following to your `deps`: ``` {:ecto_mysql_extras, "~> 0.3"}, ``` ### [](ecto_stats.html#sqlite)SQLite To enable the "Ecto Stats" functionality for SQLite in your dashboard, you will need to do the three steps below: 1. Add the [`ecto_sqlite3_extras`](../ecto_sqlite3_extras.html) dependency 2. (optional) Configure the dashboard #### Add the `ecto_sqlite3_extras` dependency In your `mix.exs`, add the following to your `deps`: ``` {:ecto_sqlite3_extras, "~> 1.2.0"}, ``` ### [](ecto_stats.html#configure-the-dashboard)Configure the dashboard This step is **only needed if you want to restrict the repositories** listed in your dashboard, because by default all *repos* are gonna be listed. Go to the `live_dashboard` call in your router and list your repositories: ``` live_dashboard "/dashboard", ecto_repos: [MyApp.Repo] ``` You want to list all repositories that connect to distinct databases. For example, if you have both `MyApp.Repo` and `MyApp.RepoAnother` but they connect to the same database, there is no benefit in listing both. Remember only Ecto repositories running on [`Ecto.Adapters.Postgres`](../ecto_sql/3.12.1/Ecto.Adapters.Postgres.html) or [`Ecto.Adapters.MyXQL`](../ecto_sql/3.12.1/Ecto.Adapters.MyXQL.html) are currently supported. If you want to disable the "Ecto Stats" option altogether, set `ecto_repos: []`. Some queries such as `long_running_queries` can be configured by passing an extra `ecto_psql_extras_options` for PostgreSQL or `ecto_mysql_extras_options` for MySQL/MariaDB, which is a keyword where: - each key is the name of the query - each value is itself a keyword to be passed as `args` For example, if you want to configure the threshold for `long_running_queries`: #### PostgreSQL example ``` live_dashboard "/dashboard", ecto_repos: [MyApp.Repo], ecto_psql_extras_options: [long_running_queries: [threshold: "200 milliseconds"]] ``` See the [`ecto_psql_extras` documentation](../ecto_psql_extras/readme.html#usage) for available options. #### MySQL/MariaDB example ``` live_dashboard "/dashboard", ecto_repos: [MyApp.Repo], ecto_mysql_extras_options: [long_running_queries: [threshold: 200]] ``` See the [`ecto_mysql_extras` documentation](../ecto_mysql_extras/readme.html#usage) for available options. #### SQLite example ``` live_dashboard "/dashboard", ecto_repos: [MyApp.Repo], ecto_sqlite3_extras_options: [] ``` See the [`ecto_sqlite3_extras` documentation](https://github.com/orsinium-labs/ecto_sqlite3_extras) for available options. ### [](ecto_stats.html#install-custom-postgresql-extensions)Install custom PostgreSQL extensions Once the repository page is enabled, some of the queries (Calls and Outliers) require the [pg\_stat\_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) extension to be installed. If you wish to access said functionality, you must install the extension first, otherwise said functionality won't be shown. ### [](ecto_stats.html#mysql-mariadb-configuration)MySQL/MariaDB configuration The user which is accessing the repo should have access to the certain system level databases. See the [`ecto_mysql_extras` documentation](../ecto_mysql_extras/readme.html#mysql-mariadb-configuration) for more details which schemas are being used. For MariaDB the `performance_schema` isn't enabled by default. To enable this add `performance_schema=ON` to `my.cnf`. These changes take effect after a restart. See the [`ecto_mysql_extras` documentation](../ecto_mysql_extras/readme.html#performance-schema) for more details. [← Previous Page API Reference](api-reference.html) [Next Page → Configuring metrics](metrics.html) [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) ([current file](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6/show/guides/ecto_stats.md)) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/metrics.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/guides/metrics.md#L1 "View Source") Configuring metrics This guide covers how to install and configure your LiveDashboard Metrics. ## [](metrics.html#installing-metrics)Installing metrics To enable the "Metrics" functionality in your dashboard, you will need to do the three steps below: 1. Add the telemetry dependencies 2. Define your telemetry module 3. Configure the dashboard ### [](metrics.html#add-the-telemetry-dependencies)Add the telemetry dependencies In your `mix.exs`, add the following to your `deps`: ``` {:telemetry_poller, "~> 0.4"}, {:telemetry_metrics, "~> 0.4"}, ``` If you generated your Phoenix app in version v1.5+, these dependencies will already be installed. You can also skip the next section. ### [](metrics.html#define-your-telemetry-module)Define your telemetry module In your Phoenix application, we recommend that you create a module to act as your telemetry supervision tree. Within this supervisor you can define your application's metrics and start your reporters. The example below contains the child spec for a LiveDashboard reporter, as well as some metrics definitions for telemetry events emitted by Phoenix, Ecto, and the VM (via the `:telemetry_poller` package). Create your Telemetry module in `lib/my_app_web/telemetry.ex`: ``` defmodule MyAppWeb.Telemetry do use Supervisor import Telemetry.Metrics def start_link(arg) do Supervisor.start_link(__MODULE__, arg, name: __MODULE__) end @impl true def init(_arg) do children = [ # Telemetry poller will execute the given period measurements # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} # Add reporters as children of your supervision tree. # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} ] Supervisor.init(children, strategy: :one_for_one) end def metrics do [ # Phoenix Metrics summary("phoenix.endpoint.stop.duration", unit: {:native, :millisecond} ), summary("phoenix.router_dispatch.stop.duration", tags: [:route], unit: {:native, :millisecond} ), # Database Time Metrics summary("my_app.repo.query.total_time", unit: {:native, :millisecond}), summary("my_app.repo.query.decode_time", unit: {:native, :millisecond}), summary("my_app.repo.query.query_time", unit: {:native, :millisecond}), summary("my_app.repo.query.queue_time", unit: {:native, :millisecond}), summary("my_app.repo.query.idle_time", unit: {:native, :millisecond}), # VM Metrics summary("vm.memory.total", unit: {:byte, :kilobyte}), summary("vm.total_run_queue_lengths.total"), summary("vm.total_run_queue_lengths.cpu"), summary("vm.total_run_queue_lengths.io") ] end defp periodic_measurements do [] end end ``` Make sure to replace `MyApp` and `my_app` by your actual application name. Then add to your main application's supervision tree (usually in `lib/my_app/application.ex`): ``` children = [ MyApp.Repo, MyAppWeb.Telemetry, MyAppWeb.Endpoint, ... ] ``` ### [](metrics.html#configure-the-dashboard)Configure the dashboard The last step now is to configure the dashboard. Go to the `live_dashboard` call in your router and add the following option: ``` live_dashboard "/dashboard", metrics: MyAppWeb.Telemetry ``` Now refresh the "/dashboard" page and the metrics functionality should be enabled. Each metric goes to a distinct group based on the metric name itself. ## [](metrics.html#more-about-telemetry)More about telemetry Now that you have metrics up and running, you can begin exploring the rest of the telemetry ecosystem! Here are a few links to get you started: - The [`Telemetry.Metrics`](../telemetry_metrics.html) module documentation contains more information on: - Metric types - Breaking down metrics by tags - VM Metrics - Custom periodic polling - For a deeper dive into Phoenix and Ecto metrics, see our [Telemetry Walkthrough](../phoenix/telemetry.html). - For more Elixir libraries using `:telemetry`, see [Libraries using Telemetry](../phoenix/telemetry.html#libraries-using-telemetry). ## [](metrics.html#configure-metrics)Configure Metrics The LiveDashboard integrates with `:telemetry` converting each [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) to a beautiful, real-time chart. The following table shows how [`Telemetry.Metrics`](../telemetry_metrics/1.0.0/Telemetry.Metrics.html) metrics map to LiveDashboard charts: Telemetry.MetricsY-Axis Value(s)`last_value`Always set to an absolute value`counter`Always increased by 1`sum`Always increased/decreased by an absolute value`summary`Value/Min/Max/Avg`distribution`Total number of events in individual buckets ### [](metrics.html#reporter-options)Reporter options Reporter options can be given to each metric as an option. For example: ``` counter("my_app.counter", reporter_options: [...]) ``` The following reporter options are available to the dashboard: - `:nav` - configures the group the metric belongs to. By default the group is the first part of the name. For example, `counter("my_app.counter")` defaults to group "my\_app" - `:prune_threshold` - the maximum number of data points. When the threshold is reached, the chart data will be pruned by half. Default is `1_000`. - `:bucket_size` - the unit width of each bucket. This option only applies to `distribution` histograms. The default value is `20`. ### [](metrics.html#metrics-history)Metrics history [Metrics history can also be enabled via a custom configuration](metrics_history.html). [← Previous Page Configuring Ecto repository stats](ecto_stats.html) [Next Page → Configuring metrics history](metrics_history.html) [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) ([current file](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6/show/guides/metrics.md)) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/metrics_history.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/guides/metrics_history.md#L1 "View Source") Configuring metrics history If you wish to populate metrics with history saved from telemetry or another data source, modify the dashboard config (in "my\_app\_web/router.ex") to include a `metrics_history` key like so: ``` live_dashboard "/dashboard", metrics: MyAppWeb.Telemetry, metrics_history: {MyApp.MetricsStorage, :metrics_history, []} ``` where `MetricsStorage` is a module and `:metrics_history` is a function taking a single argument in this example, which will always be a metric. The function must return a list, empty if there is no data, or a list of maps with `:label`, `:measurement` and `:time` keys in every map. The function [`Phoenix.LiveDashboard.extract_datapoint_for_metric/4`](Phoenix.LiveDashboard.html#extract_datapoint_for_metric/4) will return a map in exactly this format (with optional time argument if you want to override the default of `System.system_time(:microsecond)`), or it may return `nil` in which case the data point should not be saved. You could store the data in an ETS table or in Redis or the database, or anywhere else, but for this example we'll use a GenServer, with a [circular buffer](https://en.wikipedia.org/wiki/Circular_buffer) to emit recent telemetry when each client connects. In your `mix.exs`, add the following to your `deps`: ``` {:circular_buffer, "~> 0.4.0"}, ``` Then add the following module "lib/my\_app\_web/metrics\_storage.ex": ``` defmodule MyAppWeb.MetricsStorage do use GenServer @history_buffer_size 50 def metrics_history(metric) do GenServer.call(__MODULE__, {:data, metric}) end def start_link(args) do GenServer.start_link(__MODULE__, args, name: __MODULE__) end @impl true def init(metrics) do Process.flag(:trap_exit, true) metric_histories_map = metrics |> Enum.map(fn metric -> attach_handler(metric) {metric, CircularBuffer.new(@history_buffer_size)} end) |> Map.new() {:ok, metric_histories_map} end @impl true def terminate(_, metrics) do for metric <- metrics do :telemetry.detach({__MODULE__, metric, self()}) end :ok end defp attach_handler(%{event_name: name_list} = metric) do :telemetry.attach( {__MODULE__, metric, self()}, name_list, &__MODULE__.handle_event/4, metric ) end def handle_event(_event_name, data, metadata, metric) do if data = Phoenix.LiveDashboard.extract_datapoint_for_metric(metric, data, metadata) do GenServer.cast(__MODULE__, {:telemetry_metric, data, metric}) end end @impl true def handle_cast({:telemetry_metric, data, metric}, state) do {:noreply, update_in(state[metric], &CircularBuffer.insert(&1, data))} end @impl true def handle_call({:data, metric}, _from, state) do if history = state[metric] do {:reply, CircularBuffer.to_list(history), state} else {:reply, [], state} end end end ``` Finally, add the new module to your Application children, and initialize it with some or all of your metrics, such as from `MyAppWeb.Telemetry.metrics/0`. ``` # Start genserver to store transient metrics {MyAppWeb.MetricsStorage, MyAppWeb.Telemetry.metrics()}, ``` Now, when you select a tab on the Metrics dashboard, LiveDashboard will call into your module to get the metrics history for that tab. [← Previous Page Configuring metrics](metrics.html) [Next Page → Configuring OS Data](os_mon.html) [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) ([current file](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6/show/guides/metrics_history.md)) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/os_mon.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/guides/os_mon.md#L1 "View Source") Configuring OS Data This guide covers how to install and configure your LiveDashboard OS Data. ## [](os_mon.html#enabling-os_mon)Enabling `os_mon` The OS Data comes from the `os_mon` application, which ships as part of your Erlang distribution. You can start it by adding it to the extra applications section in your `mix.exs`: ``` def application do [ ..., extra_applications: [:logger, :runtime_tools, :os_mon] ] end ``` > Some operating systems break Erlang into multiple packages. In this case, you may need to install a package such as `erlang-os-mon` or similar. ## [](os_mon.html#configuring-os_mon)Configuring os\_mon See [the Erlang docs](https://www.erlang.org/doc/apps/os_mon/os_mon_app.html) for more information and `os_mon` configuration. [← Previous Page Configuring metrics history](metrics_history.html) [Next Page → Configuring request logger](request_logger.html) [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) ([current file](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6/show/guides/os_mon.md)) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_dashboard/request_logger.html.md === [LiveDashboard](http://www.phoenixframework.org) v0.8.6 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of LiveDashboard Settings # [View Source](https://github.com/phoenixframework/phoenix_live_dashboard/blob/v0.8.6/guides/request_logger.md#L1 "View Source") Configuring request logger This guide covers how to install and configure your LiveDashboard request logger. ## [](request_logger.html#installing-request-logger)Installing request logger Installing the request logger is straight-forward. Just add the following plug to your "lib/my\_app\_web/endpoint.ex", right before [`Plug.RequestId`](../plug/1.16.1/Plug.RequestId.html): ``` plug Phoenix.LiveDashboard.RequestLogger, param_key: "request_logger", cookie_key: "request_logger" ``` If your application is an API only, then you most likely don't use cookies, which means you can remove the "cookie\_key" option. Now go to your dashboard and the Request Logger will be enabled. Once you click it, you will have the option to either pass a parameter to stream requests logs or to enable/disable a cookie that streams requests logs. ## [](request_logger.html#configuring-request-logger)Configuring request logger There is no configuration for the request logger at the moment. [← Previous Page Configuring OS Data](os_mon.html) [Hex Package](https://hex.pm/packages/phoenix_live_dashboard/0.8.6) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6) ([current file](https://preview.hex.pm/preview/phoenix_live_dashboard/0.8.6/show/guides/request_logger.md)) Search HexDocs [Download ePub version](LiveDashboard.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.Component.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1 "View Source") Phoenix.Component (Phoenix LiveView v1.0.2) Define reusable function components with HEEx templates. A function component is any function that receives an assigns map as an argument and returns a rendered struct built with [the `~H` sigil](Phoenix.Component.html#sigil_H/2): ``` defmodule MyComponent do # In Phoenix apps, the line is typically: use MyAppWeb, :html use Phoenix.Component def greet(assigns) do ~H""" <p>Hello, {@name}!</p> """ end end ``` This function uses the `~H` sigil to return a rendered template. `~H` stands for HEEx (HTML + EEx). HEEx is a template language for writing HTML mixed with Elixir interpolation. We can write Elixir code inside `{...}` for HTML-aware interpolation inside tag attributes and the body. We can also interpolate arbitrary HEEx blocks using `<%= ... %>` We use `@name` to access the key `name` defined inside `assigns`. When invoked within a `~H` sigil or HEEx template file: ``` <MyComponent.greet name="Jane" /> ``` The following HTML is rendered: ``` <p>Hello, Jane!</p> ``` If the function component is defined locally, or its module is imported, then the caller can invoke the function directly without specifying the module: ``` <.greet name="Jane" /> ``` For dynamic values, you can interpolate Elixir expressions into a function component: ``` <.greet name={@user.name} /> ``` Function components can also accept blocks of HEEx content (more on this later): ``` <.card> <p>This is the body of my card!</p> </.card> ``` In this module we will learn how to build rich and composable components to use in our applications. ## [](Phoenix.Component.html#module-attributes)Attributes [`Phoenix.Component`](Phoenix.Component.html) provides the [`attr/3`](Phoenix.Component.html#attr/3) macro to declare what attributes the proceeding function component expects to receive when invoked: ``` attr :name, :string, required: true def greet(assigns) do ~H""" <p>Hello, {@name}!</p> """ end ``` By calling [`attr/3`](Phoenix.Component.html#attr/3), it is now clear that `greet/1` requires a string attribute called `name` present in its assigns map to properly render. Failing to do so will result in a compilation warning: ``` <MyComponent.greet /> <!-- warning: missing required attribute "name" for component MyAppWeb.MyComponent.greet/1 lib/app_web/my_component.ex:15 --> ``` Attributes can provide default values that are automatically merged into the assigns map: ``` attr :name, :string, default: "Bob" ``` Now you can invoke the function component without providing a value for `name`: ``` <.greet /> ``` Rendering the following HTML: ``` <p>Hello, Bob!</p> ``` Accessing an attribute which is required and does not have a default value will fail. You must explicitly declare `default: nil` or assign a value programmatically with the [`assign_new/3`](Phoenix.Component.html#assign_new/3) function. Multiple attributes can be declared for the same function component: ``` attr :name, :string, required: true attr :age, :integer, required: true def celebrate(assigns) do ~H""" <p> Happy birthday {@name}! You are {@age} years old. </p> """ end ``` Allowing the caller to pass multiple values: ``` <.celebrate name={"Genevieve"} age={34} /> ``` Rendering the following HTML: ``` <p> Happy birthday Genevieve! You are 34 years old. </p> ``` Multiple function components can be defined in the same module, with different attributes. In the following example, `<Components.greet/>` requires a `name`, but *does not* require a `title`, and `<Components.heading>` requires a `title`, but *does not* require a `name`. ``` defmodule Components do # In Phoenix apps, the line is typically: use MyAppWeb, :html use Phoenix.Component attr :title, :string, required: true def heading(assigns) do ~H""" <h1>{@title}</h1> """ end attr :name, :string, required: true def greet(assigns) do ~H""" <p>Hello {@name}</p> """ end end ``` With the [`attr/3`](Phoenix.Component.html#attr/3) macro you have the core ingredients to create reusable function components. But what if you need your function components to support dynamic attributes, such as common HTML attributes to mix into a component's container? ## [](Phoenix.Component.html#module-global-attributes)Global attributes Global attributes are a set of attributes that a function component can accept when it declares an attribute of type `:global`. By default, the set of attributes accepted are those attributes common to all standard HTML tags. See [Global attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes) for a complete list of attributes. Once a global attribute is declared, any number of attributes in the set can be passed by the caller without having to modify the function component itself. Below is an example of a function component that accepts a dynamic number of global attributes: ``` attr :message, :string, required: true attr :rest, :global def notification(assigns) do ~H""" <span {@rest}>{@message}</span> """ end ``` The caller can pass multiple global attributes (such as `phx-*` bindings or the `class` attribute): ``` <.notification message="You've got mail!" class="bg-green-200" phx-click="close" /> ``` Rendering the following HTML: ``` <span class="bg-green-200" phx-click="close">You've got mail!</span> ``` Note that the function component did not have to explicitly declare a `class` or `phx-click` attribute in order to render. Global attributes can define defaults which are merged with attributes provided by the caller. For example, you may declare a default `class` if the caller does not provide one: ``` attr :rest, :global, default: %{class: "bg-blue-200"} ``` Now you can call the function component without a `class` attribute: ``` <.notification message="You've got mail!" phx-click="close" /> ``` Rendering the following HTML: ``` <span class="bg-blue-200" phx-click="close">You've got mail!</span> ``` Note that the global attribute cannot be provided directly and doing so will emit a warning. In other words, this is invalid: ``` <.notification message="You've got mail!" rest={%{"phx-click" => "close"}} /> ``` ### [](Phoenix.Component.html#module-included-globals)Included globals You may also specify which attributes are included in addition to the known globals with the `:include` option. For example to support the `form` attribute on a button component: ``` # <.button form="my-form"/> attr :rest, :global, include: ~w(form) slot :inner_block def button(assigns) do ~H""" <button {@rest}>{render_slot(@inner_block)}</button> """ end ``` The `:include` option is useful to apply global additions on a case-by-case basis, but sometimes you want to extend existing components with new global attributes, such as Alpine.js' `x-` prefixes, which we'll outline next. ### [](Phoenix.Component.html#module-custom-global-attribute-prefixes)Custom global attribute prefixes You can extend the set of global attributes by providing a list of attribute prefixes to `use Phoenix.Component`. Like the default attributes common to all HTML elements, any number of attributes that start with a global prefix will be accepted by function components invoked by the current module. By default, the following prefixes are supported: `phx-`, `aria-`, and `data-`. For example, to support the `x-` prefix used by [Alpine.js](https://alpinejs.dev/), you can pass the `:global_prefixes` option to `use Phoenix.Component`: ``` use Phoenix.Component, global_prefixes: ~w(x-) ``` In your Phoenix application, this is typically done in your `lib/my_app_web.ex` file, inside the `def html` definition: ``` def html do quote do use Phoenix.Component, global_prefixes: ~w(x-) # ... end end ``` Now all function components invoked by this module will accept any number of attributes prefixed with `x-`, in addition to the default global prefixes. You can learn more about attributes by reading the documentation for [`attr/3`](Phoenix.Component.html#attr/3). ## [](Phoenix.Component.html#module-slots)Slots In addition to attributes, function components can accept blocks of HEEx content, referred to as slots. Slots enable further customization of the rendered HTML, as the caller can pass the function component HEEx content they want the component to render. [`Phoenix.Component`](Phoenix.Component.html) provides the [`slot/3`](Phoenix.Component.html#slot/3) macro used to declare slots for function components: ``` slot :inner_block, required: true def button(assigns) do ~H""" <button> {render_slot(@inner_block)} </button> """ end ``` The expression `render_slot(@inner_block)` renders the HEEx content. You can invoke this function component like so: ``` <.button> This renders <strong>inside</strong> the button! </.button> ``` Which renders the following HTML: ``` <button> This renders <strong>inside</strong> the button! </button> ``` Like the [`attr/3`](Phoenix.Component.html#attr/3) macro, using the [`slot/3`](Phoenix.Component.html#slot/3) macro will provide compile-time validations. For example, invoking `button/1` without a slot of HEEx content will result in a compilation warning being emitted: ``` <.button /> <!-- warning: missing required slot "inner_block" for component MyAppWeb.MyComponent.button/1 lib/app_web/my_component.ex:15 --> ``` ### [](Phoenix.Component.html#module-the-default-slot)The default slot The example above uses the default slot, accessible as an assign named `@inner_block`, to render HEEx content via the [`render_slot/1`](Phoenix.Component.html#render_slot/1) function. If the values rendered in the slot need to be dynamic, you can pass a second value back to the HEEx content by calling [`render_slot/2`](Phoenix.Component.html#render_slot/2): ``` slot :inner_block, required: true attr :entries, :list, default: [] def unordered_list(assigns) do ~H""" <ul :for={entry <- @entries}> <li>{render_slot(@inner_block, entry)}</li> </ul> """ end ``` When invoking the function component, you can use the special attribute `:let` to take the value that the function component passes back and bind it to a variable: ``` <.unordered_list :let={fruit} entries={~w(apples bananas cherries)}> I like <b>{fruit}</b>! </.unordered_list> ``` Rendering the following HTML: ``` <ul> <li>I like <b>apples</b>!</li> <li>I like <b>bananas</b>!</li> <li>I like <b>cherries</b>!</li> </ul> ``` Now the separation of concerns is maintained: the caller can specify multiple values in a list attribute without having to specify the HEEx content that surrounds and separates them. ### [](Phoenix.Component.html#module-named-slots)Named slots In addition to the default slot, function components can accept multiple, named slots of HEEx content. For example, imagine you want to create a modal that has a header, body, and footer: ``` slot :header slot :inner_block, required: true slot :footer, required: true def modal(assigns) do ~H""" <div class="modal"> <div class="modal-header"> {render_slot(@header) || "Modal"} </div> <div class="modal-body"> {render_slot(@inner_block)} </div> <div class="modal-footer"> {render_slot(@footer)} </div> </div> """ end ``` You can invoke this function component using the named slot HEEx syntax: ``` <.modal> This is the body, everything not in a named slot is rendered in the default slot. <:footer> This is the bottom of the modal. </:footer> </.modal> ``` Rendering the following HTML: ``` <div class="modal"> <div class="modal-header"> Modal. </div> <div class="modal-body"> This is the body, everything not in a named slot is rendered in the default slot. </div> <div class="modal-footer"> This is the bottom of the modal. </div> </div> ``` As shown in the example above, [`render_slot/1`](Phoenix.Component.html#render_slot/1) returns `nil` when an optional slot is declared and none is given. This can be used to attach default behaviour. ### [](Phoenix.Component.html#module-slot-attributes)Slot attributes Unlike the default slot, it is possible to pass a named slot multiple pieces of HEEx content. Named slots can also accept attributes, defined by passing a block to the [`slot/3`](Phoenix.Component.html#slot/3) macro. If multiple pieces of content are passed, [`render_slot/2`](Phoenix.Component.html#render_slot/2) will merge and render all the values. Below is a table component illustrating multiple named slots with attributes: ``` slot :column, doc: "Columns with column labels" do attr :label, :string, required: true, doc: "Column label" end attr :rows, :list, default: [] def table(assigns) do ~H""" <table> <tr> <th :for={col <- @column}>{col.label}</th> </tr> <tr :for={row <- @rows}> <td :for={col <- @column}>{render_slot(col, row)}</td> </tr> </table> """ end ``` You can invoke this function component like so: ``` <.table rows={[%{name: "Jane", age: "34"}, %{name: "Bob", age: "51"}]}> <:column :let={user} label="Name"> {user.name} </:column> <:column :let={user} label="Age"> {user.age} </:column> </.table> ``` Rendering the following HTML: ``` <table> <tr> <th>Name</th> <th>Age</th> </tr> <tr> <td>Jane</td> <td>34</td> </tr> <tr> <td>Bob</td> <td>51</td> </tr> </table> ``` You can learn more about slots and the [`slot/3`](Phoenix.Component.html#slot/3) macro [in its documentation](Phoenix.Component.html#slot/3). ## [](Phoenix.Component.html#module-embedding-external-template-files)Embedding external template files The [`embed_templates/1`](Phoenix.Component.html#embed_templates/1) macro can be used to embed `.html.heex` files as function components. The directory path is based on the current module (`__DIR__`), and a wildcard pattern may be used to select all files within a directory tree. For example, imagine a directory listing: ``` ├── components.ex ├── cards │ ├── pricing_card.html.heex │ └── features_card.html.heex ``` Then you can embed the page templates in your `components.ex` module and call them like any other function component: ``` defmodule MyAppWeb.Components do use Phoenix.Component embed_templates "cards/*" def landing_hero(assigns) do ~H""" <.pricing_card /> <.features_card /> """ end end ``` See [`embed_templates/1`](Phoenix.Component.html#embed_templates/1) for more information, including declarative assigns support for embedded templates. ## [](Phoenix.Component.html#module-debug-annotations)Debug Annotations HEEx templates support debug annotations, which are special HTML comments that wrap around rendered components to help you identify where markup in your HTML document is rendered within your function component tree. For example, imagine the following HEEx template: ``` <.header> <.button>Click</.button> </.header> ``` The HTML document would receive the following comments when debug annotations are enabled: ``` <!-- @caller lib/app_web/home_live.ex:20 --> <!-- <AppWeb.CoreComponents.header> lib/app_web/core_components.ex:123 --> <header class="p-5"> <!-- @caller lib/app_web/home_live.ex:48 --> <!-- <AppWeb.CoreComponents.button> lib/app_web/core_components.ex:456 --> <button class="px-2 bg-indigo-500 text-white">Click</button> <!-- </AppWeb.CoreComponents.button> --> </header> <!-- </AppWeb.CoreComponents.header> --> ``` Debug annotations work across any `~H` or `.html.heex` template. They can be enabled globally with the following configuration in your `config/dev.exs` file: ``` config :phoenix_live_view, debug_heex_annotations: true ``` Changing this configuration will require [`mix clean`](../mix/Mix.Tasks.Clean.html) and a full recompile. # [](Phoenix.Component.html#summary)Summary ## [Components](Phoenix.Component.html#components) [async\_result(assigns)](Phoenix.Component.html#async_result/1) Renders an async assign with slots for the different loading states. The result state takes precedence over subsequent loading and failed states. [dynamic\_tag(assigns)](Phoenix.Component.html#dynamic_tag/1) Generates a dynamically named HTML tag. [focus\_wrap(assigns)](Phoenix.Component.html#focus_wrap/1) Wraps tab focus around a container for accessibility. [form(assigns)](Phoenix.Component.html#form/1) Renders a form. [inputs\_for(assigns)](Phoenix.Component.html#inputs_for/1) Renders nested form inputs for associations or embeds. [intersperse(assigns)](Phoenix.Component.html#intersperse/1) Intersperses separator slot between an enumerable. [link(assigns)](Phoenix.Component.html#link/1) Generates a link to a given route. [live\_component(assigns)](Phoenix.Component.html#live_component/1) A function component for rendering [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) within a parent LiveView. [live\_file\_input(assigns)](Phoenix.Component.html#live_file_input/1) Builds a file input tag for a LiveView upload. [live\_img\_preview(assigns)](Phoenix.Component.html#live_img_preview/1) Generates an image preview on the client for a selected file. [live\_title(assigns)](Phoenix.Component.html#live_title/1) Renders a title with automatic prefix/suffix on `@page_title` updates. ## [Macros](Phoenix.Component.html#macros) [attr(name, type, opts \\\\ \[\])](Phoenix.Component.html#attr/3) Declares attributes for a HEEx function components. [embed\_templates(pattern, opts \\\\ \[\])](Phoenix.Component.html#embed_templates/2) Embeds external template files into the module as function components. [sigil\_H(arg, modifiers)](Phoenix.Component.html#sigil_H/2) The `~H` sigil for writing HEEx templates inside source files. [slot(name, opts \\\\ \[\])](Phoenix.Component.html#slot/2) Declares a slot. See [`slot/3`](Phoenix.Component.html#slot/3) for more information. [slot(name, opts, block)](Phoenix.Component.html#slot/3) Declares a function component slot. ## [Functions](Phoenix.Component.html#functions) [assign(socket\_or\_assigns, keyword\_or\_map)](Phoenix.Component.html#assign/2) Adds key-value pairs to assigns. [assign(socket\_or\_assigns, key, value)](Phoenix.Component.html#assign/3) Adds a `key`-`value` pair to `socket_or_assigns`. [assign\_new(socket\_or\_assigns, key, fun)](Phoenix.Component.html#assign_new/3) Assigns the given `key` with value from `fun` into `socket_or_assigns` if one does not yet exist. [assigns\_to\_attributes(assigns, exclude \\\\ \[\])](Phoenix.Component.html#assigns_to_attributes/2) Filters the assigns as a list of keywords for use in dynamic tag attributes. [changed?(socket\_or\_assigns, key)](Phoenix.Component.html#changed?/2) Checks if the given key changed in `socket_or_assigns`. [live\_flash(other, key)](Phoenix.Component.html#live_flash/2) deprecated Returns the flash message from the LiveView flash assign. [live\_render(conn\_or\_socket, view, opts \\\\ \[\])](Phoenix.Component.html#live_render/3) Renders a LiveView within a template. [render\_slot(slot, argument \\\\ nil)](Phoenix.Component.html#render_slot/2) Renders a slot entry with the given optional `argument`. [to\_form(data\_or\_params, options \\\\ \[\])](Phoenix.Component.html#to_form/2) Converts a given data structure to a [`Phoenix.HTML.Form`](../phoenix_html/4.1.1/Phoenix.HTML.Form.html). [update(socket\_or\_assigns, key, fun)](Phoenix.Component.html#update/3) Updates an existing `key` with `fun` in the given `socket_or_assigns`. [upload\_errors(conf)](Phoenix.Component.html#upload_errors/1) Returns errors for the upload as a whole. [upload\_errors(conf, entry)](Phoenix.Component.html#upload_errors/2) Returns errors for the upload entry. [used\_input?(form\_field)](Phoenix.Component.html#used_input?/1) Checks if the input field was used by the client. # [](Phoenix.Component.html#components)Components [](Phoenix.Component.html#async_result/1) # async\_result(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L3308) Renders an async assign with slots for the different loading states. The result state takes precedence over subsequent loading and failed states. *Note*: The inner block receives the result of the async assign as a :let. The let is only accessible to the inner block and is not in scope to the other slots. ## [](Phoenix.Component.html#async_result/1-examples)Examples ``` <.async_result :let={org} assign={@org}> <:loading>Loading organization...</:loading> <:failed :let={_failure}>there was an error loading the organization</:failed> <%= if org do %> {org.name} <% else %> You don't have an organization yet. <% end %> </.async_result> ``` To display loading and failed states again on subsequent `assign_async` calls, reset the assign to a result-free `%AsyncResult{}`: ``` {:noreply, socket |> assign_async(:page, :data, &reload_data/0) |> assign(:page, AsyncResult.loading())} ``` ## [](Phoenix.Component.html#async_result/1-attributes)Attributes - `assign` ([`Phoenix.LiveView.AsyncResult`](Phoenix.LiveView.AsyncResult.html)) (required) ## [](Phoenix.Component.html#async_result/1-slots)Slots - `loading` - rendered while the assign is loading for the first time. - `failed` - rendered when an error or exit is caught or assign\_async returns `{:error, reason}` for the first time. Receives the error as a `:let`. - `inner_block` - rendered when the assign is loaded successfully via `AsyncResult.ok/2`. Receives the result as a `:let`. [](Phoenix.Component.html#dynamic_tag/1) # dynamic\_tag(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L3045) Generates a dynamically named HTML tag. Raises an [`ArgumentError`](../elixir/ArgumentError.html) if the tag name is found to be unsafe HTML. ## [](Phoenix.Component.html#dynamic_tag/1-attributes)Attributes - `tag_name` (`:string`) (required) - The name of the tag, such as `div`. - `name` (`:string`) - Deprecated: use tag\_name instead. If tag\_name is used, passed to the tag. Otherwise the name of the tag, such as `div`. - Global attributes are accepted. Additional HTML attributes to add to the tag, ensuring proper escaping. ## [](Phoenix.Component.html#dynamic_tag/1-slots)Slots - `inner_block` ## [](Phoenix.Component.html#dynamic_tag/1-examples)Examples ``` <.dynamic_tag tag_name="input" name="my-input" type="text"/> ``` ``` <input name="my-input" type="text"/> ``` ``` <.dynamic_tag tag_name="p">content</.dynamic_tag> ``` ``` <p>content</p> ``` [](Phoenix.Component.html#focus_wrap/1) # focus\_wrap(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L2993) Wraps tab focus around a container for accessibility. This is an essential accessibility feature for interfaces such as modals, dialogs, and menus. ## [](Phoenix.Component.html#focus_wrap/1-attributes)Attributes - `id` (`:string`) (required) - The DOM identifier of the container tag. - Global attributes are accepted. Additional HTML attributes to add to the container tag. ## [](Phoenix.Component.html#focus_wrap/1-slots)Slots - `inner_block` (required) - The content rendered inside of the container tag. ## [](Phoenix.Component.html#focus_wrap/1-examples)Examples Simply render your inner content within this component and focus will be wrapped around the container as the user tabs through the containers content: ``` <.focus_wrap id="my-modal" class="bg-white"> <div id="modal-content"> Are you sure? <button phx-click="cancel">Cancel</button> <button phx-click="confirm">OK</button> </div> </.focus_wrap> ``` [](Phoenix.Component.html#form/1) # form(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L2331) Renders a form. This function receives a [`Phoenix.HTML.Form`](../phoenix_html/4.1.1/Phoenix.HTML.Form.html) struct, generally created with [`to_form/2`](Phoenix.Component.html#to_form/2), and generates the relevant form tags. It can be used either inside LiveView or outside. > To see how forms work in practice, you can run `mix phx.gen.live Blog Post posts title body:text` inside your Phoenix application, which will setup the necessary database tables and LiveViews to manage your data. ## [](Phoenix.Component.html#form/1-examples-inside-liveview)Examples: inside LiveView Inside LiveViews, this function component is typically called with as `for={@form}`, where `@form` is the result of the [`to_form/1`](Phoenix.Component.html#to_form/1) function. [`to_form/1`](Phoenix.Component.html#to_form/1) expects either a map or an [`Ecto.Changeset`](https://hexdocs.pm/ecto/Ecto.Changeset.html) as the source of data and normalizes it into [`Phoenix.HTML.Form`](../phoenix_html/4.1.1/Phoenix.HTML.Form.html) structure. For example, you may use the parameters received in a [`Phoenix.LiveView.handle_event/3`](Phoenix.LiveView.html#c:handle_event/3) callback to create an Ecto changeset and then use [`to_form/1`](Phoenix.Component.html#to_form/1) to convert it to a form. Then, in your templates, you pass the `@form` as argument to `:for`: ``` <.form for={@form} phx-change="change_name" > <.input field={@form[:email]} /> </.form> ``` The `.input` component is generally defined as part of your own application and adds all styling necessary: ``` def input(assigns) do ~H""" <input type="text" name={@field.name} id={@field.id} value={@field.value} class="..." /> """ end ``` A form accepts multiple options. For example, if you are doing file uploads and you want to capture submissions, you might write instead: ``` <.form for={@form} multipart phx-change="change_user" phx-submit="save_user" > ... <input type="submit" value="Save" /> </.form> ``` Notice how both examples use `phx-change`. The LiveView must implement the `phx-change` event and store the input values as they arrive on change. This is important because, if an unrelated change happens on the page, LiveView should re-render the inputs with their updated values. Without `phx-change`, the inputs would otherwise be cleared. Alternatively, you can use `phx-update="ignore"` on the form to discard any updates. ### [](Phoenix.Component.html#form/1-using-the-for-attribute)Using the `for` attribute The `for` attribute can also be a map or an Ecto.Changeset. In such cases, a form will be created on the fly, and you can capture it using `:let`: ``` <.form :let={form} for={@changeset} phx-change="change_user" > ``` However, such approach is discouraged in LiveView for two reasons: - LiveView can better optimize your code if you access the form fields using `@form[:field]` rather than through the let-variable `form` - Ecto changesets are meant to be single use. By never storing the changeset in the assign, you will be less tempted to use it across operations ### [](Phoenix.Component.html#form/1-a-note-on-errors)A note on `:errors` Even if `changeset.errors` is non-empty, errors will not be displayed in a form if [the changeset `:action`](https://hexdocs.pm/ecto/Ecto.Changeset.html#module-changeset-actions) is `nil` or `:ignore`. This is useful for things like validation hints on form fields, e.g. an empty changeset for a new form. That changeset isn't valid, but we don't want to show errors until an actual user action has been performed. For example, if the user submits and a `Repo.insert/1` is called and fails on changeset validation, the action will be set to `:insert` to show that an insert was attempted, and the presence of that action will cause errors to be displayed. The same is true for Repo.update/delete. Error visibility is handled by providing the action to [`to_form/2`](Phoenix.Component.html#to_form/2), which will set the underlying changeset action. You can also set the action manually by directly updating on the `Ecto.Changeset` struct field, or by using `Ecto.Changeset.apply_action/2`. Since the action can be arbitrary, you can set it to `:validate` or anything else to avoid giving the impression that a database operation has actually been attempted. ### [](Phoenix.Component.html#form/1-displaying-errors-on-used-and-unused-input-fields)Displaying errors on used and unused input fields Used inputs are only those inputs that have been focused, interacted with, or submitted by the client. In most cases, a user shouldn't receive error feedback for forms they haven't yet interacted with, until they submit the form. Filtering the errors based on used input fields can be done with [`used_input?/1`](Phoenix.Component.html#used_input?/1). ## [](Phoenix.Component.html#form/1-example-outside-liveview-regular-http-requests)Example: outside LiveView (regular HTTP requests) The `form` component can still be used to submit forms outside of LiveView. In such cases, the standard HTML `action` attribute MUST be given. Without said attribute, the `form` method and csrf token are discarded. ``` <.form :let={f} for={@changeset} action={~p"/comments/#{@comment}"}> <.input field={f[:body]} /> </.form> ``` In the example above, we passed a changeset to `for` and captured the value using `:let={f}`. This approach is ok outside of LiveViews, as there are no change tracking optimizations to consider. ### [](Phoenix.Component.html#form/1-csrf-protection)CSRF protection CSRF protection is a mechanism to ensure that the user who rendered the form is the one actually submitting it. This module generates a CSRF token by default. Your application should check this token on the server to avoid attackers from making requests on your server on behalf of other users. Phoenix by default checks this token. When posting a form with a host in its address, such as "//host.com/path" instead of only "/path", Phoenix will include the host signature in the token and validate the token only if the accessed host is the same as the host in the token. This is to avoid tokens from leaking to third party applications. If this behaviour is problematic, you can generate a non-host specific token with [`Plug.CSRFProtection.get_csrf_token/0`](../plug/1.16.1/Plug.CSRFProtection.html#get_csrf_token/0) and pass it to the form generator via the `:csrf_token` option. ## [](Phoenix.Component.html#form/1-attributes)Attributes - `for` (`:any`) (required) - An existing form or the form source data. - `action` (`:string`) - The action to submit the form on. This attribute must be given if you intend to submit the form to a URL without LiveView. - `as` (`:atom`) - The prefix to be used in names and IDs generated by the form. For example, setting `as: :user_params` means the parameters will be nested "user\_params" in your `handle_event` or `conn.params["user_params"]` for regular HTTP requests. If you set this option, you must capture the form with `:let`. - `csrf_token` (`:any`) - A token to authenticate the validity of requests. One is automatically generated when an action is given and the method is not `get`. When set to `false`, no token is generated. - `errors` (`:list`) - Use this to manually pass a keyword list of errors to the form. This option is useful when a regular map is given as the form source and it will make the errors available under `f.errors`. If you set this option, you must capture the form with `:let`. - `method` (`:string`) - The HTTP method. It is only used if an `:action` is given. If the method is not `get` nor `post`, an input tag with name `_method` is generated alongside the form tag. If an `:action` is given with no method, the method will default to the return value of [`Phoenix.HTML.FormData.to_form/2`](../phoenix_html/4.1.1/Phoenix.HTML.FormData.html#to_form/2) (usually `post`). - `multipart` (`:boolean`) - Sets `enctype` to `multipart/form-data`. Required when uploading files. Defaults to `false`. - Global attributes are accepted. Additional HTML attributes to add to the form tag. Supports all globals plus: `["autocomplete", "name", "rel", "enctype", "novalidate", "target"]`. ## [](Phoenix.Component.html#form/1-slots)Slots - `inner_block` (required) - The content rendered inside of the form tag. [](Phoenix.Component.html#inputs_for/1) # inputs\_for(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L2646) Renders nested form inputs for associations or embeds. ## [](Phoenix.Component.html#inputs_for/1-attributes)Attributes - `field` ([`Phoenix.HTML.FormField`](../phoenix_html/4.1.1/Phoenix.HTML.FormField.html)) (required) - A %Phoenix.HTML.Form{}/field name tuple, for example: {@form\[:email]}. - `id` (`:string`) - The id base to be used in the form inputs. Defaults to the parent form id. The computed id will be the concatenation of the base id with the field name, along with a book keeping index for each input in the list. - `as` (`:atom`) - The name to be used in the form, defaults to the concatenation of the given field to the parent form name. - `default` (`:any`) - The value to use if none is available. - `prepend` (`:list`) - The values to prepend when rendering. This only applies if the field value is a list and no parameters were sent through the form. - `append` (`:list`) - The values to append when rendering. This only applies if the field value is a list and no parameters were sent through the form. - `skip_hidden` (`:boolean`) - Skip the automatic rendering of hidden fields to allow for more tight control over the generated markup. Defaults to `false`. - `options` (`:list`) - Any additional options for the [`Phoenix.HTML.FormData`](../phoenix_html/4.1.1/Phoenix.HTML.FormData.html) protocol implementation. Defaults to `[]`. ## [](Phoenix.Component.html#inputs_for/1-slots)Slots - `inner_block` (required) - The content rendered for each nested form. ## [](Phoenix.Component.html#inputs_for/1-examples)Examples ``` <.form for={@form} phx-change="change_name" > <.inputs_for :let={f_nested} field={@form[:nested]}> <.input type="text" field={f_nested[:name]} /> </.inputs_for> </.form> ``` ## [](Phoenix.Component.html#inputs_for/1-dynamically-adding-and-removing-inputs)Dynamically adding and removing inputs Dynamically adding and removing inputs is supported by rendering named buttons for inserts and removals. Like inputs, buttons with name/value pairs are serialized with form data on change and submit events. Libraries such as Ecto, or custom param filtering can then inspect the parameters and handle the added or removed fields. This can be combined with `Ecto.Changeset.cast_assoc/3`'s `:sort_param` and `:drop_param` options. For example, imagine a parent with an `:emails` `has_many` or `embeds_many` association. To cast the user input from a nested form, one simply needs to configure the options: ``` schema "mailing_lists" do field :title, :string embeds_many :emails, EmailNotification, on_replace: :delete do field :email, :string field :name, :string end end def changeset(list, attrs) do list |> cast(attrs, [:title]) |> cast_embed(:emails, with: &email_changeset/2, sort_param: :emails_sort, drop_param: :emails_drop ) end ``` Here we see the `:sort_param` and `:drop_param` options in action. > Note: `on_replace: :delete` on the `has_many` and `embeds_many` is required when using these options. When Ecto sees the specified sort or drop parameter from the form, it will sort the children based on the order they appear in the form, add new children it hasn't seen, or drop children if the parameter instructs it to do so. The markup for such a schema and association would look like this: ``` <.inputs_for :let={ef} field={@form[:emails]}> <input type="hidden" name="mailing_list[emails_sort][]" value={ef.index} /> <.input type="text" field={ef[:email]} placeholder="email" /> <.input type="text" field={ef[:name]} placeholder="name" /> <button type="button" name="mailing_list[emails_drop][]" value={ef.index} phx-click={JS.dispatch("change")} > <.icon name="hero-x-mark" class="w-6 h-6 relative top-2" /> </button> </.inputs_for> <input type="hidden" name="mailing_list[emails_drop][]" /> <button type="button" name="mailing_list[emails_sort][]" value="new" phx-click={JS.dispatch("change")}> add more </button> ``` We used `inputs_for` to render inputs for the `:emails` association, which contains an email address and name input for each child. Within the nested inputs, we render a hidden `mailing_list[emails_sort][]` input, which is set to the index of the given child. This tells Ecto's cast operation how to sort existing children, or where to insert new children. Next, we render the email and name inputs as usual. Then we render a button containing the "delete" text with the name `mailing_list[emails_drop][]`, containing the index of the child as its value. Like before, this tells Ecto to delete the child at this index when the button is clicked. We use `phx-click={JS.dispatch("change")}` on the button to tell LiveView to treat this button click as a change event, rather than a submit event on the form, which invokes our form's `phx-change` binding. Outside the `inputs_for`, we render an empty `mailing_list[emails_drop][]` input, to ensure that all children are deleted when saving a form where the user dropped all entries. This hidden input is required whenever dropping associations. Finally, we also render another button with the sort param name `mailing_list[emails_sort][]` and `value="new"` name with accompanied "add more" text. Please note that this button must have `type="button"` to prevent it from submitting the form. Ecto will treat unknown sort params as new children and build a new child. This button is optional and only necessary if you want to dynamically add entries. You can optionally add a similar button before the `<.inputs_for>`, in the case you want to prepend entries. ### [](Phoenix.Component.html#inputs_for/1-a-note-on-accessing-a-field-s-value)A note on accessing a field's `value` You may be tempted to access `form[:field].value` or attempt to manipulate the form metadata in your templates. However, bear in mind that the `form[:field]` value reflects the most recent changes. For example, an `:integer` field may either contain integer values, but it may also hold a string, if the form has been submitted. This is particularly noticeable when using `inputs_for`. Accessing the `.value` of a nested field may either return a struct, a changeset, or raw parameters sent by the client (when using `drop_param`). This makes the `form[:field].value` impractical for deriving or computing other properties. The correct way to approach this problem is by computing any property either in your LiveViews, by traversing the relevant changesets and data structures, or by moving the logic to the `Ecto.Changeset` itself. As an example, imagine you are building a time tracking application where: - users enter the total work time for a day - individual activities are tracked as embeds - the sum of all activities should match the total time - the form should display the remaining time Instead of trying to calculate the remaining time in your template by doing something like `calculate_remaining(@form)` and accessing `form[:activities].value`, calculate the remaining time based on the changeset in your `handle_event` instead: ``` def handle_event("validate", %{"tracked_day" => params}, socket) do changeset = TrackedDay.changeset(socket.assigns.tracked_day, params) remaining = calculate_remaining(changeset) {:noreply, assign(socket, form: to_form(changeset, action: :validate), remaining: remaining)} end # Helper function to calculate remaining time defp calculate_remaining(changeset) do total = Ecto.Changeset.get_field(changeset) activities = Ecto.Changeset.get_embed(changeset, :activities) Enum.reduce(activities, total, fn activity, acc -> duration = case activity do %{valid?: true} = changeset -> Ecto.Changeset.get_field(changeset, :duration) # if the activity is invalid, we don't include its duration in the calculation _ -> 0 end acc - length end) end ``` This logic might also be implemented directly in your schema module and, if you often need the `:remaining` value, you could also add it as a `:virtual` field to your schema and run the calculation when validating the changeset: ``` def changeset(tracked_day, attrs) do tracked_day |> cast(attrs, [:total_duration]) |> cast_embed(:activities) |> validate_required([:total_duration]) |> validate_number(:total_duration, greater_than: 0) |> validate_and_put_remaining_time() end defp validate_and_put_remaining_time(changeset) do remaining = calculate_remaining(changeset) put_change(changeset, :remaining, remaining) end ``` By using this approach, you can safely render the remaining time in your template using `@form[:remaining].value`, avoiding the pitfalls of directly accessing complex field values. [](Phoenix.Component.html#intersperse/1) # intersperse(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L3249) Intersperses separator slot between an enumerable. Useful when you need to add a separator between items such as when rendering breadcrumbs for navigation. Provides each item to the inner block. ## [](Phoenix.Component.html#intersperse/1-examples)Examples ``` <.intersperse :let={item} enum={["home", "profile", "settings"]}> <:separator> <span class="sep">|</span> </:separator> {item} </.intersperse> ``` Renders the following markup: ``` home <span class="sep">|</span> profile <span class="sep">|</span> settings ``` ## [](Phoenix.Component.html#intersperse/1-attributes)Attributes - `enum` (`:any`) (required) - the enumerable to intersperse with separators. ## [](Phoenix.Component.html#intersperse/1-slots)Slots - `inner_block` (required) - the inner\_block to render for each item. - `separator` (required) - the slot for the separator. [](Phoenix.Component.html#link/1) # link(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L2914) Generates a link to a given route. It is typically used with one of the three attributes: - `patch` - on click, it patches the current LiveView with the given path - `navigate` - on click, it navigates to a new LiveView at the given path - `href` - on click, it performs traditional browser navigation (as any `<a>` tag) ## [](Phoenix.Component.html#link/1-attributes)Attributes - `navigate` (`:string`) - Navigates to a LiveView. When redirecting across LiveViews, the browser page is kept, but a new LiveView process is mounted and its contents is loaded on the page. It is only possible to navigate between LiveViews declared under the same router [`live_session`](Phoenix.LiveView.Router.html#live_session/3). When used outside of a LiveView or across live sessions, it behaves like a regular browser redirect. - `patch` (`:string`) - Patches the current LiveView. The `handle_params` callback of the current LiveView will be invoked and the minimum content will be sent over the wire, as any other LiveView diff. - `href` (`:any`) - Uses traditional browser navigation to the new location. This means the whole page is reloaded on the browser. - `replace` (`:boolean`) - When using `:patch` or `:navigate`, should the browser's history be replaced with `pushState`? Defaults to `false`. - `method` (`:string`) - The HTTP method to use with the link. This is intended for usage outside of LiveView and therefore only works with the `href={...}` attribute. It has no effect on `patch` and `navigate` instructions. In case the method is not `get`, the link is generated inside the form which sets the proper information. In order to submit the form, JavaScript must be enabled in the browser. Defaults to `"get"`. - `csrf_token` (`:any`) - A boolean or custom token to use for links with an HTTP method other than `get`. Defaults to `true`. - Global attributes are accepted. Additional HTML attributes added to the `a` tag. Supports all globals plus: `["download", "hreflang", "referrerpolicy", "rel", "target", "type"]`. ## [](Phoenix.Component.html#link/1-slots)Slots - `inner_block` (required) - The content rendered inside of the `a` tag. ## [](Phoenix.Component.html#link/1-examples)Examples ``` <.link href="/">Regular anchor link</.link> ``` ``` <.link navigate={~p"/"} class="underline">home</.link> ``` ``` <.link navigate={~p"/?sort=asc"} replace={false}> Sort By Price </.link> ``` ``` <.link patch={~p"/details"}>view details</.link> ``` ``` <.link href={URI.parse("https://elixir-lang.org")}>hello</.link> ``` ``` <.link href="/the_world" method="delete" data-confirm="Really?">delete</.link> ``` ## [](Phoenix.Component.html#link/1-javascript-dependency)JavaScript dependency In order to support links where `:method` is not `"get"` or use the above data attributes, [`Phoenix.HTML`](../phoenix_html/4.1.1/Phoenix.HTML.html) relies on JavaScript. You can load `priv/static/phoenix_html.js` into your build tool. ### [](Phoenix.Component.html#link/1-data-attributes)Data attributes Data attributes are added as a keyword list passed to the `data` key. The following data attributes are supported: - `data-confirm` - shows a confirmation prompt before generating and submitting the form when `:method` is not `"get"`. ### [](Phoenix.Component.html#link/1-overriding-the-default-confirm-behaviour)Overriding the default confirm behaviour `phoenix_html.js` does trigger a custom event `phoenix.link.click` on the clicked DOM element when a click happened. This allows you to intercept the event on its way bubbling up to `window` and do your own custom logic to enhance or replace how the `data-confirm` attribute is handled. You could for example replace the browsers `confirm()` behavior with a custom javascript implementation: ``` // Compared to a javascript window.confirm, the custom dialog does not block // javascript execution. Therefore to make this work as expected we store // the successful confirmation as an attribute and re-trigger the click event. // On the second click, the `data-confirm-resolved` attribute is set and we proceed. const RESOLVED_ATTRIBUTE = "data-confirm-resolved"; // listen on document.body, so it's executed before the default of // phoenix_html, which is listening on the window object document.body.addEventListener('phoenix.link.click', function (e) { // Prevent default implementation e.stopPropagation(); // Introduce alternative implementation var message = e.target.getAttribute("data-confirm"); if(!message){ return; } // Confirm is resolved execute the click event if (e.target?.hasAttribute(RESOLVED_ATTRIBUTE)) { e.target.removeAttribute(RESOLVED_ATTRIBUTE); return; } // Confirm is needed, preventDefault and show your modal e.preventDefault(); e.target?.setAttribute(RESOLVED_ATTRIBUTE, ""); vex.dialog.confirm({ message: message, callback: function (value) { if (value == true) { // Customer confirmed, re-trigger the click event. e.target?.click(); } else { // Customer canceled e.target?.removeAttribute(RESOLVED_ATTRIBUTE); } } }) }, false); ``` Or you could attach your own custom behavior. ``` window.addEventListener('phoenix.link.click', function (e) { // Introduce custom behaviour var message = e.target.getAttribute("data-prompt"); var answer = e.target.getAttribute("data-prompt-answer"); if(message && answer && (answer != window.prompt(message))) { e.preventDefault(); } }, false); ``` The latter could also be bound to any `click` event, but this way you can be sure your custom code is only executed when the code of `phoenix_html.js` is run. ## [](Phoenix.Component.html#link/1-csrf-protection)CSRF Protection By default, CSRF tokens are generated through [`Plug.CSRFProtection`](../plug/1.16.1/Plug.CSRFProtection.html). [](Phoenix.Component.html#live_component/1) # live\_component(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L2035) A function component for rendering [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) within a parent LiveView. While LiveViews can be nested, each LiveView starts its own process. A LiveComponent provides similar functionality to LiveView, except they run in the same process as the LiveView, with its own encapsulated state. That's why they are called stateful components. ## [](Phoenix.Component.html#live_component/1-attributes)Attributes - `id` (`:string`) (required) - A unique identifier for the LiveComponent. Note the `id` won't necessarily be used as the DOM `id`. That is up to the component to decide. - `module` (`:atom`) (required) - The LiveComponent module to render. Any additional attributes provided will be passed to the LiveComponent as a map of assigns. See [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) for more information. ## [](Phoenix.Component.html#live_component/1-examples)Examples ``` <.live_component module={MyApp.WeatherComponent} id="thermostat" city="Kraków" /> ``` [](Phoenix.Component.html#live_file_input/1) # live\_file\_input(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L3147) Builds a file input tag for a LiveView upload. ## [](Phoenix.Component.html#live_file_input/1-attributes)Attributes - `upload` ([`Phoenix.LiveView.UploadConfig`](Phoenix.LiveView.UploadConfig.html)) (required) - The [`Phoenix.LiveView.UploadConfig`](Phoenix.LiveView.UploadConfig.html) struct. - `accept` (`:string`) - the optional override for the accept attribute. Defaults to :accept specified by allow\_upload. - Global attributes are accepted. Supports all globals plus: `["webkitdirectory", "required", "disabled", "capture", "form"]`. Note the `id` attribute cannot be overwritten, but you can create a label with a `for` attribute pointing to the UploadConfig `ref`. ## [](Phoenix.Component.html#live_file_input/1-drag-and-drop)Drag and Drop Drag and drop is supported by annotating the droppable container with a `phx-drop-target` attribute pointing to the UploadConfig `ref`, so the following markup is all that is required for drag and drop support: ``` <div class="container" phx-drop-target={@uploads.avatar.ref}> <!-- ... --> <.live_file_input upload={@uploads.avatar} /> </div> ``` ## [](Phoenix.Component.html#live_file_input/1-examples)Examples Rendering a file input: ``` <.live_file_input upload={@uploads.avatar} /> ``` Rendering a file input with a label: ``` <label for={@uploads.avatar.ref}>Avatar</label> <.live_file_input upload={@uploads.avatar} /> ``` [](Phoenix.Component.html#live_img_preview/1) # live\_img\_preview(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L3206) Generates an image preview on the client for a selected file. ## [](Phoenix.Component.html#live_img_preview/1-attributes)Attributes - `entry` ([`Phoenix.LiveView.UploadEntry`](Phoenix.LiveView.UploadEntry.html)) (required) - The [`Phoenix.LiveView.UploadEntry`](Phoenix.LiveView.UploadEntry.html) struct. - `id` (`:string`) - the id of the img tag. Derived by default from the entry ref, but can be overridden as needed if you need to render a preview of the same entry multiple times on the same page. Defaults to `nil`. - Global attributes are accepted. ## [](Phoenix.Component.html#live_img_preview/1-examples)Examples ``` <.live_img_preview :for={entry <- @uploads.avatar.entries} entry={entry} width="75" /> ``` When you need to use it multiple times, make sure that they have distinct ids ``` <.live_img_preview :for={entry <- @uploads.avatar.entries} entry={entry} width="75" /> <.live_img_preview :for={entry <- @uploads.avatar.entries} id={"modal-#{entry.ref}"} entry={entry} width="500" /> ``` [](Phoenix.Component.html#live_title/1) # live\_title(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L2099) Renders a title with automatic prefix/suffix on `@page_title` updates. ## [](Phoenix.Component.html#live_title/1-attributes)Attributes - `prefix` (`:string`) - A prefix added before the content of `inner_block`. Defaults to `nil`. - `default` (`:string`) - The default title to use if the inner block is empty on regular or connected mounts.*Note*: empty titles, such as `nil` or an empty string, fallsback to the default value. Defaults to `nil`. - `suffix` (`:string`) - A suffix added after the content of `inner_block`. Defaults to `nil`. ## [](Phoenix.Component.html#live_title/1-slots)Slots - `inner_block` (required) - Content rendered inside the `title` tag. ## [](Phoenix.Component.html#live_title/1-examples)Examples ``` <.live_title default="Welcome" prefix="MyApp · "> {assigns[:page_title]} </.live_title> ``` ``` <.live_title default="Welcome" suffix=" · MyApp"> {assigns[:page_title]} </.live_title> ``` # [](Phoenix.Component.html#macros)Macros [](Phoenix.Component.html#attr/3) # attr(name, type, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1978)(macro) Declares attributes for a HEEx function components. ## [](Phoenix.Component.html#attr/3-arguments)Arguments - `name` - an atom defining the name of the attribute. Note that attributes cannot define the same name as any other attributes or slots declared for the same component. - `type` - an atom defining the type of the attribute. - `opts` - a keyword list of options. Defaults to `[]`. ### [](Phoenix.Component.html#attr/3-types)Types An attribute is declared by its name, type, and options. The following types are supported: NameDescription`:any`any term (including `nil`)`:string`any binary string`:atom`any atom (including `true`, `false`, and `nil`)`:boolean`any boolean`:integer`any integer`:float`any float`:list`any list of any arbitrary types`:map`any map of any arbitrary types`:global`any common HTML attributes, plus those defined by `:global_prefixes`A struct moduleany module that defines a struct with [`defstruct/1`](../elixir/Kernel.html#defstruct/1) Note only `:any` and `:atom` expect the value to be set to `nil`. ### [](Phoenix.Component.html#attr/3-options)Options - `:required` - marks an attribute as required. If a caller does not pass the given attribute, a compile warning is issued. - `:default` - the default value for the attribute if not provided. If this option is not set and the attribute is not given, accessing the attribute will fail unless a value is explicitly set with [`assign_new/3`](Phoenix.Component.html#assign_new/3). - `:examples` - a non-exhaustive list of values accepted by the attribute, used for documentation purposes. - `:values` - an exhaustive list of values accepted by the attributes. If a caller passes a literal not contained in this list, a compile warning is issued. - `:doc` - documentation for the attribute. ## [](Phoenix.Component.html#attr/3-compile-time-validations)Compile-Time Validations LiveView performs some validation of attributes via the `:phoenix_live_view` compiler. When attributes are defined, LiveView will warn at compilation time on the caller if: - A required attribute of a component is missing. - An unknown attribute is given. - You specify a literal attribute (such as `value="string"` or `value`, but not `value={expr}`) and the type does not match. The following types currently support literal validation: `:string`, `:atom`, `:boolean`, `:integer`, `:float`, `:map` and `:list`. - You specify a literal attribute and it is not a member of the `:values` list. LiveView does not perform any validation at runtime. This means the type information is mostly used for documentation and reflection purposes. On the side of the LiveView component itself, defining attributes provides the following quality of life improvements: - The default value of all attributes will be added to the `assigns` map upfront. - Attribute documentation is generated for the component. - Required struct types are annotated and emit compilation warnings. For example, if you specify `attr :user, User, required: true` and then you write `@user.non_valid_field` in your template, a warning will be emitted. - Calls made to the component are tracked for reflection and validation purposes. ## [](Phoenix.Component.html#attr/3-documentation-generation)Documentation Generation Public function components that define attributes will have their attribute types and docs injected into the function's documentation, depending on the value of the `@doc` module attribute: - if `@doc` is a string, the attribute docs are injected into that string. The optional placeholder `[INSERT LVATTRDOCS]` can be used to specify where in the string the docs are injected. Otherwise, the docs are appended to the end of the `@doc` string. - if `@doc` is unspecified, the attribute docs are used as the default `@doc` string. - if `@doc` is `false`, the attribute docs are omitted entirely. The injected attribute docs are formatted as a markdown list: - `name` (`:type`) (required) - attr docs. Defaults to `:default`. By default, all attributes will have their types and docs injected into the function `@doc` string. To hide a specific attribute, you can set the value of `:doc` to `false`. ## [](Phoenix.Component.html#attr/3-example)Example ``` attr :name, :string, required: true attr :age, :integer, required: true def celebrate(assigns) do ~H""" <p> Happy birthday {@name}! You are {@age} years old. </p> """ end ``` [](Phoenix.Component.html#embed_templates/2) # embed\_templates(pattern, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1683)(macro) Embeds external template files into the module as function components. ## [](Phoenix.Component.html#embed_templates/2-options)Options - `:root` - The root directory to embed files. Defaults to the current module's directory (`__DIR__`) - `:suffix` - A string value to append to embedded function names. By default, function names will be the name of the template file excluding the format and engine. A wildcard pattern may be used to select all files within a directory tree. For example, imagine a directory listing: ``` ├── components.ex ├── pages │ ├── about_page.html.heex │ └── welcome_page.html.heex ``` Then to embed the page templates in your `components.ex` module: ``` defmodule MyAppWeb.Components do use Phoenix.Component embed_templates "pages/*" end ``` Now, your module will have an `about_page/1` and `welcome_page/1` function component defined. Embedded templates also support declarative assigns via bodyless function definitions, for example: ``` defmodule MyAppWeb.Components do use Phoenix.Component embed_templates "pages/*" attr :name, :string, required: true def welcome_page(assigns) slot :header def about_page(assigns) end ``` Multiple invocations of `embed_templates` is also supported, which can be useful if you have more than one template format. For example: ``` defmodule MyAppWeb.Emails do use Phoenix.Component embed_templates "emails/*.html", suffix: "_html" embed_templates "emails/*.text", suffix: "_text" end ``` Note: this function is the same as [`Phoenix.Template.embed_templates/2`](../phoenix_template/1.0.4/Phoenix.Template.html#embed_templates/2). It is also provided here for convenience and documentation purposes. Therefore, if you want to embed templates for other formats, which are not related to [`Phoenix.Component`](Phoenix.Component.html), prefer to `import Phoenix.Template, only: [embed_templates: 1]` than this module. [](Phoenix.Component.html#sigil_H/2) # sigil\_H(arg, modifiers) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L795)(macro) The `~H` sigil for writing HEEx templates inside source files. `HEEx` is a HTML-aware and component-friendly extension of Elixir Embedded language ([`EEx`](../eex/EEx.html)) that provides: - Built-in handling of HTML attributes - An HTML-like notation for injecting function components - Compile-time validation of the structure of the template - The ability to minimize the amount of data sent over the wire - Out-of-the-box code formatting via [`mix format`](../mix/Mix.Tasks.Format.html) ## [](Phoenix.Component.html#sigil_H/2-example)Example ``` ~H""" <div title="My div" class={@class}> <p>Hello {@name}</p> <MyApp.Weather.city name="Kraków"/> </div> """ ``` ## [](Phoenix.Component.html#sigil_H/2-syntax)Syntax `HEEx` is built on top of Embedded Elixir ([`EEx`](../eex/EEx.html)). In this section, we are going to cover the basic constructs in `HEEx` templates as well as its syntax extensions. ### [](Phoenix.Component.html#sigil_H/2-interpolation)Interpolation `HEEx` allows using `{...}` for HTML-aware interpolation, inside tag attributes as well as the body: ``` <p>Hello, {@name}</p> ``` If you want to interpolate an attribute, you write: ``` <div class={@class}> ... </div> ``` You can put any Elixir expression between `{ ... }`. For example, if you want to set classes, where some are static and others are dynamic, you can using string interpolation: ``` <div class={"btn btn-#{@type}"}> ... </div> ``` The following attribute values have special meaning on HTML tags: - `true` - if a value is `true`, the attribute is rendered with no value at all. For example, `<input required={true}>` is the same as `<input required>`; - `false` or `nil` - if a value is `false` or `nil`, the attribute is omitted. Note the `class` and `style` attributes will be rendered as empty strings, instead of ommitted, which has the same effect as not rendering them, but allows for rendering optimizations. - `list` (only for the `class` attribute) - each element of the list is processed as a different class. `nil` and `false` elements are discarded. For multiple dynamic attributes, you can use the same notation but without assigning the expression to any specific attribute: ``` <div {@dynamic_attrs}> ... </div> ``` In this case, the expression inside `{...}` must be either a keyword list or a map containing the key-value pairs representing the dynamic attributes. ### [](Phoenix.Component.html#sigil_H/2-interpolating-blocks)Interpolating blocks The curly braces syntax is the default mechanism for interpolating code. However, it cannot be used in all scenarios, in particular: - Curly braces cannot be used inside `<script>` and `<style>` tags, as that would make writing JS and CSS quite tedious. You can also fully disable curly braces interpolation in a given tag and its children by adding the `phx-no-curly-interpolation` attribute - it does not support multiline block constructs, such as `if`, `case`, and similar For example, if you need to interpolate a string inside a script tag, you could do: ``` <script> window.URL = "<%= @my_url %>" </script> ``` Similarly, for block constructs in Elixir, you can write: ``` <%= if @show_greeting? do %> <p>Hello, {@name}</p> <% end %> ``` However, for conditionals and for-comprehensions, there are built-in constructs in HEEx too, which we will explore next. #### Curly braces in text within tag bodies If you have text in your tag bodies, which includes curly braces you can use `&lbrace;` or `<%= "{" %>` to prevent them from being considered the start of interpolation. ### [](Phoenix.Component.html#sigil_H/2-special-attributes)Special attributes Apart from normal HTML attributes, HEEx also supports some special attributes such as `:let` and `:for`. #### :let This is used by components and slots that want to yield a value back to the caller. For an example, see how [`form/1`](Phoenix.Component.html#form/1) works: ``` <.form :let={f} for={@form} phx-change="validate" phx-submit="save"> <.input field={f[:username]} type="text" /> ... </.form> ``` Notice how the variable `f`, defined by `.form` is used by your `input` component. The [`Phoenix.Component`](Phoenix.Component.html) module has detailed documentation on how to use and implement such functionality. #### :if and :for It is a syntax sugar for `<%= if .. do %>` and `<%= for .. do %>` that can be used in regular HTML, function components, and slots. For example in an HTML tag: ``` <table id="admin-table" :if={@admin?}> <tr :for={user <- @users}> <td>{user.name}</td> </tr> <table> ``` The snippet above will only render the table if `@admin?` is true, and generate a `tr` per user as you would expect from the collection. `:for` can be used similarly in function components: ``` <.error :for={msg <- @errors} message={msg}/> ``` Which is equivalent to writing: ``` <%= for msg <- @errors do %> <.error message={msg} /> <% end %> ``` And `:for` in slots behaves the same way: ``` <.table id="my-table" rows={@users}> <:col :for={header <- @headers} :let={user}> <td>{user[header]}</td> </:col> <table> ``` You can also combine `:for` and `:if` for tags, components, and slot to act as a filter: ``` <.error :for={msg <- @errors} :if={msg != nil} message={msg} /> ``` Note that unlike Elixir's regular `for`, HEEx' `:for` does not support multiple generators in one expression. In such cases, you must use [`EEx`](../eex/EEx.html)'s blocks. ### [](Phoenix.Component.html#sigil_H/2-function-components)Function components Function components are stateless components implemented as pure functions with the help of the [`Phoenix.Component`](Phoenix.Component.html) module. They can be either local (same module) or remote (external module). `HEEx` allows invoking these function components directly in the template using an HTML-like notation. For example, a remote function: ``` <MyApp.Weather.city name="Kraków"/> ``` A local function can be invoked with a leading dot: ``` <.city name="Kraków"/> ``` where the component could be defined as follows: ``` defmodule MyApp.Weather do use Phoenix.Component def city(assigns) do ~H""" The chosen city is: {@name}. """ end def country(assigns) do ~H""" The chosen country is: {@name}. """ end end ``` It is typically best to group related functions into a single module, as opposed to having many modules with a single `render/1` function. Function components support other important features, such as slots. You can learn more about components in [`Phoenix.Component`](Phoenix.Component.html). ## [](Phoenix.Component.html#sigil_H/2-code-formatting)Code formatting You can automatically format HEEx templates (.heex) and `~H` sigils using [`Phoenix.LiveView.HTMLFormatter`](Phoenix.LiveView.HTMLFormatter.html). Please check that module for more information. [](Phoenix.Component.html#slot/2) # slot(name, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1848)(macro) Declares a slot. See [`slot/3`](Phoenix.Component.html#slot/3) for more information. [](Phoenix.Component.html#slot/3) # slot(name, opts, block) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1829)(macro) Declares a function component slot. ## [](Phoenix.Component.html#slot/3-arguments)Arguments - `name` - an atom defining the name of the slot. Note that slots cannot define the same name as any other slots or attributes declared for the same component. - `opts` - a keyword list of options. Defaults to `[]`. - `block` - a code block containing calls to [`attr/3`](Phoenix.Component.html#attr/3). Defaults to `nil`. ### [](Phoenix.Component.html#slot/3-options)Options - `:required` - marks a slot as required. If a caller does not pass a value for a required slot, a compilation warning is emitted. Otherwise, an omitted slot will default to `[]`. - `:validate_attrs` - when set to `false`, no warning is emitted when a caller passes attributes to a slot defined without a do block. If not set, defaults to `true`. - `:doc` - documentation for the slot. Any slot attributes declared will have their documentation listed alongside the slot. ### [](Phoenix.Component.html#slot/3-slot-attributes)Slot Attributes A named slot may declare attributes by passing a block with calls to [`attr/3`](Phoenix.Component.html#attr/3). Unlike attributes, slot attributes cannot accept the `:default` option. Passing one will result in a compile warning being issued. ### [](Phoenix.Component.html#slot/3-the-default-slot)The Default Slot The default slot can be declared by passing `:inner_block` as the `name` of the slot. Note that the `:inner_block` slot declaration cannot accept a block. Passing one will result in a compilation error. ## [](Phoenix.Component.html#slot/3-compile-time-validations)Compile-Time Validations LiveView performs some validation of slots via the `:phoenix_live_view` compiler. When slots are defined, LiveView will warn at compilation time on the caller if: - A required slot of a component is missing. - An unknown slot is given. - An unknown slot attribute is given. On the side of the function component itself, defining attributes provides the following quality of life improvements: - Slot documentation is generated for the component. - Calls made to the component are tracked for reflection and validation purposes. ## [](Phoenix.Component.html#slot/3-documentation-generation)Documentation Generation Public function components that define slots will have their docs injected into the function's documentation, depending on the value of the `@doc` module attribute: - if `@doc` is a string, the slot docs are injected into that string. The optional placeholder `[INSERT LVATTRDOCS]` can be used to specify where in the string the docs are injected. Otherwise, the docs are appended to the end of the `@doc` string. - if `@doc` is unspecified, the slot docs are used as the default `@doc` string. - if `@doc` is `false`, the slot docs are omitted entirely. The injected slot docs are formatted as a markdown list: - `name` (required) - slot docs. Accepts attributes: - `name` (`:type`) (required) - attr docs. Defaults to `:default`. By default, all slots will have their docs injected into the function `@doc` string. To hide a specific slot, you can set the value of `:doc` to `false`. ## [](Phoenix.Component.html#slot/3-example)Example ``` slot :header slot :inner_block, required: true slot :footer def modal(assigns) do ~H""" <div class="modal"> <div class="modal-header"> {render_slot(@header) || "Modal"} </div> <div class="modal-body"> {render_slot(@inner_block)} </div> <div class="modal-footer"> {render_slot(@footer) || submit_button()} </div> </div> """ end ``` As shown in the example above, [`render_slot/1`](Phoenix.Component.html#render_slot/1) returns `nil` when an optional slot is declared and none is given. This can be used to attach default behaviour. # [](Phoenix.Component.html#functions)Functions [](Phoenix.Component.html#assign/2) # assign(socket\_or\_assigns, keyword\_or\_map) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1310) Adds key-value pairs to assigns. The first argument is either a LiveView `socket` or an `assigns` map from function components. A keyword list or a map of assigns must be given as argument to be merged into existing assigns. ## [](Phoenix.Component.html#assign/2-examples)Examples ``` iex> assign(socket, name: "Elixir", logo: "💧") iex> assign(socket, %{name: "Elixir"}) ``` [](Phoenix.Component.html#assign/3) # assign(socket\_or\_assigns, key, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1272) Adds a `key`-`value` pair to `socket_or_assigns`. The first argument is either a LiveView `socket` or an `assigns` map from function components. ## [](Phoenix.Component.html#assign/3-examples)Examples ``` iex> assign(socket, :name, "Elixir") ``` [](Phoenix.Component.html#assign_new/3) # assign\_new(socket\_or\_assigns, key, fun) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1205) Assigns the given `key` with value from `fun` into `socket_or_assigns` if one does not yet exist. The first argument is either a LiveView `socket` or an `assigns` map from function components. This function is useful for lazily assigning values and sharing assigns. We will cover both use cases next. ## [](Phoenix.Component.html#assign_new/3-lazy-assigns)Lazy assigns Imagine you have a function component that accepts a color: ``` <.my_component bg_color="red" /> ``` The color is also optional, so you can skip it: ``` <.my_component /> ``` In such cases, the implementation can use `assign_new` to lazily assign a color if none is given. Let's make it so it picks a random one when none is given: ``` def my_component(assigns) do assigns = assign_new(assigns, :bg_color, fn -> Enum.random(~w(bg-red-200 bg-green-200 bg-blue-200)) end) ~H""" <div class={@bg_color}> Example </div> """ end ``` ## [](Phoenix.Component.html#assign_new/3-sharing-assigns)Sharing assigns It is possible to share assigns between the Plug pipeline and LiveView on disconnected render and between parent-child LiveViews when connected. ### [](Phoenix.Component.html#assign_new/3-when-disconnected)When disconnected When a user first accesses an application using LiveView, the LiveView is first rendered in its disconnected state, as part of a regular HTML response. By using `assign_new` in the mount callback of your LiveView, you can instruct LiveView to re-use any assigns already set in `conn` during disconnected state. Imagine you have a Plug that does: ``` # A plug def authenticate(conn, _opts) do if user_id = get_session(conn, :user_id) do assign(conn, :current_user, Accounts.get_user!(user_id)) else send_resp(conn, :forbidden) end end ``` You can re-use the `:current_user` assign in your LiveView during the initial render: ``` def mount(_params, %{"user_id" => user_id}, socket) do {:ok, assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)} end ``` In such case `conn.assigns.current_user` will be used if present. If there is no such `:current_user` assign or the LiveView was mounted as part of the live navigation, where no Plug pipelines are invoked, then the anonymous function is invoked to execute the query instead. ### [](Phoenix.Component.html#assign_new/3-when-connected)When connected LiveView is also able to share assigns via `assign_new` with children LiveViews, as long as the child LiveView is also mounted when the parent LiveView is mounted. Let's see an example. If the parent LiveView defines a `:current_user` assign and the child LiveView also uses [`assign_new/3`](Phoenix.Component.html#assign_new/3) to fetch the `:current_user` in its `mount/3` callback, as in the previous subsection, the assign will be fetched from the parent LiveView, once again avoiding additional database queries. Note that `fun` also provides access to the previously assigned values: ``` assigns = assigns |> assign_new(:foo, fn -> "foo" end) |> assign_new(:bar, fn %{foo: foo} -> foo <> "bar" end) ``` Assigns sharing is performed when possible but not guaranteed. Therefore, you must ensure the result of the function given to [`assign_new/3`](Phoenix.Component.html#assign_new/3) is the same as if the value was fetched from the parent. Otherwise consider passing values to the child LiveView as part of its session. [](Phoenix.Component.html#assigns_to_attributes/2) # assigns\_to\_attributes(assigns, exclude \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L858) Filters the assigns as a list of keywords for use in dynamic tag attributes. One should prefer to use declarative assigns and `:global` attributes over this function. ## [](Phoenix.Component.html#assigns_to_attributes/2-examples)Examples Imagine the following `my_link` component which allows a caller to pass a `new_window` assign, along with any other attributes they would like to add to the element, such as class, data attributes, etc: ``` <.my_link to="/" id={@id} new_window={true} class="my-class">Home</.my_link> ``` We could support the dynamic attributes with the following component: ``` def my_link(assigns) do target = if assigns[:new_window], do: "_blank", else: false extra = assigns_to_attributes(assigns, [:new_window, :to]) assigns = assigns |> assign(:target, target) |> assign(:extra, extra) ~H""" <a href={@to} target={@target} {@extra}> {render_slot(@inner_block)} </a> """ end ``` The above would result in the following rendered HTML: ``` <a href="/" target="_blank" id="1" class="my-class">Home</a> ``` The second argument (optional) to `assigns_to_attributes` is a list of keys to exclude. It typically includes reserved keys by the component itself, which either do not belong in the markup, or are already handled explicitly by the component. [](Phoenix.Component.html#changed?/2) # changed?(socket\_or\_assigns, key) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1392) Checks if the given key changed in `socket_or_assigns`. The first argument is either a LiveView `socket` or an `assigns` map from function components. ## [](Phoenix.Component.html#changed?/2-examples)Examples ``` iex> changed?(socket, :count) ``` [](Phoenix.Component.html#live_flash/2) # live\_flash(other, key) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1049) This function is deprecated. Use Phoenix.Flash.get/2 in Phoenix v1.7+. Returns the flash message from the LiveView flash assign. ## [](Phoenix.Component.html#live_flash/2-examples)Examples ``` <p class="alert alert-info">{live_flash(@flash, :info)}</p> <p class="alert alert-danger">{live_flash(@flash, :error)}</p> ``` [](Phoenix.Component.html#live_render/3) # live\_render(conn\_or\_socket, view, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L938) Renders a LiveView within a template. This is useful in two situations: - When rendering a child LiveView inside a LiveView. - When rendering a LiveView inside a regular (non-live) controller/view. ## [](Phoenix.Component.html#live_render/3-options)Options - `:session` - a map of binary keys with extra session data to be serialized and sent to the client. All session data currently in the connection is automatically available in LiveViews. You can use this option to provide extra data. Remember all session data is serialized and sent to the client, so you should always keep the data in the session to a minimum. For example, instead of storing a User struct, you should store the "user\_id" and load the User when the LiveView mounts. - `:container` - an optional tuple for the HTML tag and DOM attributes to be used for the LiveView container. For example: `{:li, style: "color: blue;"}`. By default it uses the module definition container. See the "Containers" section below for more information. - `:id` - both the DOM ID and the ID to uniquely identify a LiveView. An `:id` is automatically generated when rendering root LiveViews but it is a required option when rendering a child LiveView. - `:sticky` - an optional flag to maintain the LiveView across live redirects, even if it is nested within another LiveView. Note that this only works for LiveViews that are in the same [live\_session](Phoenix.LiveView.Router.html#live_session/3). If you are rendering the sticky view within make sure that the sticky view itself does not use the same layout. You can do so by returning `{:ok, socket, layout: false}` from mount. ## [](Phoenix.Component.html#live_render/3-examples)Examples When rendering from a controller/view, you can call: ``` {live_render(@conn, MyApp.ThermostatLive)} ``` Or: ``` {live_render(@conn, MyApp.ThermostatLive, session: %{"home_id" => @home.id})} ``` Within another LiveView, you must pass the `:id` option: ``` {live_render(@socket, MyApp.ThermostatLive, id: "thermostat")} ``` ## [](Phoenix.Component.html#live_render/3-containers)Containers When a LiveView is rendered, its contents are wrapped in a container. By default, the container is a `div` tag with a handful of LiveView-specific attributes. The container can be customized in different ways: - You can change the default `container` on `use Phoenix.LiveView`: ``` use Phoenix.LiveView, container: {:tr, id: "foo-bar"} ``` - You can override the container tag and pass extra attributes when calling `live_render` (as well as on your `live` call in your router): ``` live_render socket, MyLiveView, container: {:tr, class: "highlight"} ``` If you don't want the container to affect layout, you can use the CSS property `display: contents` or a class that applies it, like Tailwind's `.contents`. Beware if you set this to `:body`, as any content injected inside the body (such as `Phoenix.LiveReload` features) will be discarded once the LiveView connects [](Phoenix.Component.html#render_slot/2) # render\_slot(slot, argument \\\\ nil) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1000)(macro) Renders a slot entry with the given optional `argument`. ``` {render_slot(@inner_block, @form)} ``` If the slot has no entries, nil is returned. If multiple slot entries are defined for the same slot,[`render_slot/2`](Phoenix.Component.html#render_slot/2) will automatically render all entries, merging their contents. In case you want to use the entries' attributes, you need to iterate over the list to access each slot individually. For example, imagine a table component: ``` <.table rows={@users}> <:col :let={user} label="Name"> {user.name} </:col> <:col :let={user} label="Address"> {user.address} </:col> </.table> ``` At the top level, we pass the rows as an assign and we define a `:col` slot for each column we want in the table. Each column also has a `label`, which we are going to use in the table header. Inside the component, you can render the table with headers, rows, and columns: ``` def table(assigns) do ~H""" <table> <tr :for={col <- @col}> <th>{col.label}</th> </tr> <tr :for={row <- @rows}> <td :for={col <- @col}>{render_slot(col, row)}</td> </tr> </table> """ end ``` [](Phoenix.Component.html#to_form/2) # to\_form(data\_or\_params, options \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1483) Converts a given data structure to a [`Phoenix.HTML.Form`](../phoenix_html/4.1.1/Phoenix.HTML.Form.html). This is commonly used to convert a map or an Ecto changeset into a form to be given to the [`form/1`](Phoenix.Component.html#form/1) component. ## [](Phoenix.Component.html#to_form/2-creating-a-form-from-params)Creating a form from params If you want to create a form based on `handle_event` parameters, you could do: ``` def handle_event("submitted", params, socket) do {:noreply, assign(socket, form: to_form(params))} end ``` When you pass a map to [`to_form/1`](Phoenix.Component.html#to_form/1), it assumes said map contains the form parameters, which are expected to have string keys. You can also specify a name to nest the parameters: ``` def handle_event("submitted", %{"user" => user_params}, socket) do {:noreply, assign(socket, form: to_form(user_params, as: :user))} end ``` ## [](Phoenix.Component.html#to_form/2-creating-a-form-from-changesets)Creating a form from changesets When using changesets, the underlying data, form parameters, and errors are retrieved from it. The `:as` option is automatically computed too. For example, if you have a user schema: ``` defmodule MyApp.Users.User do use Ecto.Schema schema "..." do ... end end ``` And then you create a changeset that you pass to `to_form`: ``` %MyApp.Users.User{} |> Ecto.Changeset.change() |> to_form() ``` In this case, once the form is submitted, the parameters will be available under `%{"user" => user_params}`. ## [](Phoenix.Component.html#to_form/2-options)Options - `:as` - the `name` prefix to be used in form inputs - `:id` - the `id` prefix to be used in form inputs - `:errors` - keyword list of errors (used by maps exclusively) - `:action` - The action that was taken against the form. This value can be used to distinguish between different operations such as the user typing into a form for validation, or submitting a form for a database insert. For example: `to_form(changeset, action: :validate)`, or `to_form(changeset, action: :save)`. The provided action is passed to the underlying [`Phoenix.HTML.FormData`](../phoenix_html/4.1.1/Phoenix.HTML.FormData.html) implementation options. The underlying data may accept additional options when converted to forms. For example, a map accepts `:errors` to list errors, but such option is not accepted by changesets. `:errors` is a keyword of tuples in the shape of `{error_message, options_list}`. Here is an example: ``` to_form(%{"search" => nil}, errors: [search: {"Can't be blank", []}]) ``` If an existing [`Phoenix.HTML.Form`](../phoenix_html/4.1.1/Phoenix.HTML.Form.html) struct is given, the options above will override its existing values if given. Then the remaining options are merged with the existing form options. Errors in a form are only displayed if the changeset's `action` field is set (and it is not set to `:ignore`) and can be filtered by whether the fields have been used on the client or not. Refer to [a note on :errors for more information](Phoenix.Component.html#form/1-a-note-on-errors). [](Phoenix.Component.html#update/3) # update(socket\_or\_assigns, key, fun) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1354) Updates an existing `key` with `fun` in the given `socket_or_assigns`. The first argument is either a LiveView `socket` or an `assigns` map from function components. The update function receives the current key's value and returns the updated value. Raises if the key does not exist. The update function may also be of arity 2, in which case it receives the current key's value as the first argument and the current assigns as the second argument. Raises if the key does not exist. ## [](Phoenix.Component.html#update/3-examples)Examples ``` iex> update(socket, :count, fn count -> count + 1 end) iex> update(socket, :count, &(&1 + 1)) iex> update(socket, :max_users_this_session, fn current_max, %{users: users} -> ...> max(current_max, length(users)) ...> end) ``` [](Phoenix.Component.html#upload_errors/1) # upload\_errors(conf) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1074) Returns errors for the upload as a whole. For errors that apply to a specific upload entry, use [`upload_errors/2`](Phoenix.Component.html#upload_errors/2). The output is a list. The following error may be returned: - `:too_many_files` - The number of selected files exceeds the `:max_entries` constraint ## [](Phoenix.Component.html#upload_errors/1-examples)Examples ``` def upload_error_to_string(:too_many_files), do: "You have selected too many files" ``` ``` <div :for={err <- upload_errors(@uploads.avatar)} class="alert alert-danger"> {upload_error_to_string(err)} </div> ``` [](Phoenix.Component.html#upload_errors/2) # upload\_errors(conf, entry) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1106) Returns errors for the upload entry. For errors that apply to the upload as a whole, use [`upload_errors/1`](Phoenix.Component.html#upload_errors/1). The output is a list. The following errors may be returned: - `:too_large` - The entry exceeds the `:max_file_size` constraint - `:not_accepted` - The entry does not match the `:accept` MIME types - `:external_client_failure` - When external upload fails - `{:writer_failure, reason}` - When the custom writer fails with `reason` ## [](Phoenix.Component.html#upload_errors/2-examples)Examples ``` defp upload_error_to_string(:too_large), do: "The file is too large" defp upload_error_to_string(:not_accepted), do: "You have selected an unacceptable file type" defp upload_error_to_string(:external_client_failure), do: "Something went terribly wrong" ``` ``` <%= for entry <- @uploads.avatar.entries do %> <div :for={err <- upload_errors(@uploads.avatar, entry)} class="alert alert-danger"> {upload_error_to_string(err)} </div> <% end %> ``` [](Phoenix.Component.html#used_input?/1) # used\_input?(form\_field) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_component.ex#L1603) Checks if the input field was used by the client. Used inputs are only those inputs that have been focused, interacted with, or submitted by the client. For LiveView, this is used to filter errors from the [`Phoenix.HTML.FormData`](../phoenix_html/4.1.1/Phoenix.HTML.FormData.html) implementation to avoid showing "field can't be blank" in scenarios where the client hasn't yet interacted with specific fields. Used inputs are tracked internally by the client sending a sibling key derived from each input name, which indicates the inputs that remain unused on the client. For example, a form with email and title fields where only the title has been modified so far on the client, would send the following payload: ``` %{ "title" => "new title", "email" => "", "_unused_email" => "" } ``` The `_unused_email` key indicates that the email field has not been used by the client, which is used to filter errors from the UI. Nested fields are also supported. For example, a form with a nested datetime field is considered used if any of the nested parameters are used. ``` %{ "bday" => %{ "year" => "", "month" => "", "day" => "", "_unused_day" => "" } } ``` The `_unused_day` key indicates that the day field has not been used by the client, but the year and month fields have been used, meaning the birthday field as a whole was used. ## [](Phoenix.Component.html#used_input?/1-examples)Examples For example, imagine in your template you render a title and email input. On initial load the end-user begins typing the title field. The client will send the entire form payload to the server with the typed title and an empty email. The [`Phoenix.HTML.FormData`](../phoenix_html/4.1.1/Phoenix.HTML.FormData.html) implementation will consider an empty email in this scenario as invalid, but the user shouldn't see the error because they haven't yet used the email input. To handle this, [`used_input?/1`](Phoenix.Component.html#used_input?/1) can be used to filter errors from the client by referencing param metadata to distinguish between used and unused input fields. For non-LiveViews, all inputs are considered used. ``` <input type="text" name={@form[:title].name} value={@form[:title].value} /> <div :if={used_input?(@form[:title])}> <p :for={error <- @form[:title].errors}>{error}</p> </div> <input type="text" name={@form[:email].name} value={@form[:email].value} /> <div :if={used_input?(@form[:email])}> <p :for={error <- @form[:email].errors}>{error}</p> </div> ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveComponent.CID.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L545 "View Source") Phoenix.LiveComponent.CID (Phoenix LiveView v1.0.2) The struct representing an internal unique reference to the component instance, available as the `@myself` assign in live components. Read more about the uses of `@myself` in the [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) docs. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveComponent.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L1 "View Source") Phoenix.LiveComponent behaviour (Phoenix LiveView v1.0.2) LiveComponents are a mechanism to compartmentalize state, markup, and events in LiveView. LiveComponents are defined by using [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) and are used by calling [`Phoenix.Component.live_component/1`](Phoenix.Component.html#live_component/1) in a parent LiveView. They run inside the LiveView process but have their own state and life-cycle. For this reason, they are also often called "stateful components". This is a contrast to [`Phoenix.Component`](Phoenix.Component.html), also known as "function components", which are stateless and can only compartmentalize markup. The smallest LiveComponent only needs to define a [`render/1`](Phoenix.LiveComponent.html#c:render/1) function: ``` defmodule HeroComponent do # In Phoenix apps, the line is typically: use MyAppWeb, :live_component use Phoenix.LiveComponent def render(assigns) do ~H""" <div class="hero">{@content}</div> """ end end ``` A LiveComponent is rendered as: ``` <.live_component module={HeroComponent} id="hero" content={@content} /> ``` You must always pass the `module` and `id` attributes. The `id` will be available as an assign and it must be used to uniquely identify the component. All other attributes will be available as assigns inside the LiveComponent. #### Functional components or live components? Generally speaking, you should prefer function components over live components, as they are a simpler abstraction, with a smaller surface area. The use case for live components only arises when there is a need for encapsulating both event handling and additional state. ## [](Phoenix.LiveComponent.html#module-life-cycle)Life-cycle ### [](Phoenix.LiveComponent.html#module-mount-and-update)Mount and update Live components are identified by the component module and their ID. We often tie the component ID to some application based ID: ``` <.live_component module={UserComponent} id={@user.id} user={@user} /> ``` When [`live_component/1`](Phoenix.Component.html#live_component/1) is called, [`mount/1`](Phoenix.LiveComponent.html#c:mount/1) is called once, when the component is first added to the page. [`mount/1`](Phoenix.LiveComponent.html#c:mount/1) receives a `socket` as its argument. Note that this is *not* the same `socket` struct from the parent LiveView. It doesn't contain the parent LiveView's `assigns`, and updating it won't affect the parent LiveView's `socket`. Then [`update/2`](Phoenix.LiveComponent.html#c:update/2) is invoked with all of the assigns passed to [`live_component/1`](Phoenix.Component.html#live_component/1). The assigns received as the first argument to [`update/2`](Phoenix.LiveComponent.html#c:update/2) will only include those assigns given to [`live_component/1`](Phoenix.Component.html#live_component/1), and not any pre-existing assigns in `socket.assigns` such as those assigned by [`mount/1`](Phoenix.LiveComponent.html#c:mount/1). If [`update/2`](Phoenix.LiveComponent.html#c:update/2) is not defined then all assigns given to [`live_component/1`](Phoenix.Component.html#live_component/1) will simply be merged into `socket.assigns`. Both [`mount/1`](Phoenix.LiveComponent.html#c:mount/1) and [`update/2`](Phoenix.LiveComponent.html#c:update/2) must return a tuple whose first element is `:ok` and whose second element is the updated `socket`. After the component is updated, [`render/1`](Phoenix.LiveComponent.html#c:render/1) is called with all assigns. On first render, we get: ``` mount(socket) -> update(assigns, socket) -> render(assigns) ``` On further rendering: ``` update(assigns, socket) -> render(assigns) ``` Two live components with the same module and ID are treated as the same component, regardless of where they are in the page. Therefore, if you change the location of where a component is rendered within its parent LiveView, it won't be remounted. This means you can use live components to implement cards and other elements that can be moved around without losing state. A component is only discarded when the client observes it is removed from the page. Finally, the given `id` is not automatically used as the DOM ID. If you want to set a DOM ID, it is your responsibility to do so when rendering: ``` defmodule UserComponent do # In Phoenix apps, the line is typically: use MyAppWeb, :live_component use Phoenix.LiveComponent def render(assigns) do ~H""" <div id={"user-#{@id}"} class="user"> {@user.name} </div> """ end end ``` ### [](Phoenix.LiveComponent.html#module-events)Events LiveComponents can also implement the [`handle_event/3`](Phoenix.LiveComponent.html#c:handle_event/3) callback that works exactly the same as in LiveView. For a client event to reach a component, the tag must be annotated with a `phx-target`. If you want to send the event to yourself, you can simply use the `@myself` assign, which is an *internal unique reference* to the component instance: ``` <a href="#" phx-click="say_hello" phx-target={@myself}> Say hello! </a> ``` Note that `@myself` is not set for stateless components, as they cannot receive events. If you want to target another component, you can also pass an ID or a class selector to any element inside the targeted component. For example, if there is a `UserComponent` with the DOM ID of `"user-13"`, using a query selector, we can send an event to it with: ``` <a href="#" phx-click="say_hello" phx-target="#user-13"> Say hello! </a> ``` In both cases, [`handle_event/3`](Phoenix.LiveComponent.html#c:handle_event/3) will be called with the "say\_hello" event. When [`handle_event/3`](Phoenix.LiveComponent.html#c:handle_event/3) is called for a component, only the diff of the component is sent to the client, making them extremely efficient. Any valid query selector for `phx-target` is supported, provided that the matched nodes are children of a LiveView or LiveComponent, for example to send the `close` event to multiple components: ``` <a href="#" phx-click="close" phx-target="#modal, #sidebar"> Dismiss </a> ``` ### [](Phoenix.LiveComponent.html#module-update-many)Update many Live components also support an optional [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) callback as an alternative to [`update/2`](Phoenix.LiveComponent.html#c:update/2). While [`update/2`](Phoenix.LiveComponent.html#c:update/2) is called for each component individually, [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) is called with all LiveComponents of the same module being currently rendered/updated. The advantage is that you can preload data from the database using a single query for all components, instead of running one query per component. To provide a more complete understanding of why both callbacks are necessary, let's see an example. Imagine you are implementing a component and the component needs to load some state from the database. For example: ``` <.live_component module={UserComponent} id={user_id} /> ``` A possible implementation would be to load the user on the [`update/2`](Phoenix.LiveComponent.html#c:update/2) callback: ``` def update(assigns, socket) do user = Repo.get!(User, assigns.id) {:ok, assign(socket, :user, user)} end ``` However, the issue with said approach is that, if you are rendering multiple user components in the same page, you have a N+1 query problem. By using [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) instead of [`update/2`](Phoenix.LiveComponent.html#c:update/2) , we receive a list of all assigns and sockets, allowing us to update many at once: ``` def update_many(assigns_sockets) do list_of_ids = Enum.map(assigns_sockets, fn {assigns, _sockets} -> assigns.id end) users = from(u in User, where: u.id in ^list_of_ids, select: {u.id, u}) |> Repo.all() |> Map.new() Enum.map(assigns_sockets, fn {assigns, socket} -> assign(socket, :user, users[assigns.id]) end) end ``` Now only a single query to the database will be made. In fact, the `update_many/1` algorithm is a breadth-first tree traversal, which means that even for nested components, the amount of queries are kept to a minimum. Finally, note that [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) must return an updated list of sockets in the same order as they are given. If [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) is defined, [`update/2`](Phoenix.LiveComponent.html#c:update/2) is not invoked. ### [](Phoenix.LiveComponent.html#module-summary)Summary All of the life-cycle events are summarized in the diagram below. The bubble events in white are triggers that invoke the component. In blue you have component callbacks, where the underlined names represent required callbacks: ``` flowchart LR *((start)):::event-.->M WE([wait for<br>parent changes]):::event-.->M W([wait for<br>events]):::event-.->H subgraph j__transparent[" "] subgraph i[" "] direction TB M(mount/1<br><em>only once</em>):::callback M-->U M-->UM end U(update/2):::callback-->A UM(update_many/1):::callback-->A subgraph j[" "] direction TB A --> |yes| R H(handle_event/3):::callback-->A{any<br>changes?}:::diamond end A --> |no| W end R(render/1):::callback_req-->W classDef event fill:#fff,color:#000,stroke:#000 classDef diamond fill:#FFC28C,color:#000,stroke:#000 classDef callback fill:#B7ADFF,color:#000,stroke-width:0 classDef callback_req fill:#B7ADFF,color:#000,stroke-width:0,text-decoration:underline ``` ## [](Phoenix.LiveComponent.html#module-managing-state)Managing state Now that we have learned how to define and use components, as well as how to use [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) as a data loading optimization, it is important to talk about how to manage state in components. Generally speaking, you want to avoid both the parent LiveView and the LiveComponent working on two different copies of the state. Instead, you should assume only one of them to be the source of truth. Let's discuss the two different approaches in detail. Imagine a scenario where a LiveView represents a board with each card in it as a separate LiveComponent. Each card has a form to allow update of the card title directly in the component, as follows: ``` defmodule CardComponent do use Phoenix.LiveComponent def render(assigns) do ~H""" <form phx-submit="..." phx-target={@myself}> <input name="title">{@card.title}</input> ... </form> """ end ... end ``` We will see how to organize the data flow to keep either the board LiveView or the card LiveComponents as the source of truth. ### [](Phoenix.LiveComponent.html#module-liveview-as-the-source-of-truth)LiveView as the source of truth If the board LiveView is the source of truth, it will be responsible for fetching all of the cards in a board. Then it will call [`live_component/1`](Phoenix.Component.html#live_component/1) for each card, passing the card struct as argument to `CardComponent`: ``` <.live_component :for={card <- @cards} module={CardComponent} card={card} id={card.id} board_id={@id} /> ``` Now, when the user submits the form, `CardComponent.handle_event/3` will be triggered. However, if the update succeeds, you must not change the card struct inside the component. If you do so, the card struct in the component will get out of sync with the LiveView. Since the LiveView is the source of truth, you should instead tell the LiveView that the card was updated. Luckily, because the component and the view run in the same process, sending a message from the LiveComponent to the parent LiveView is as simple as sending a message to `self()`: ``` defmodule CardComponent do ... def handle_event("update_title", %{"title" => title}, socket) do send self(), {:updated_card, %{socket.assigns.card | title: title}} {:noreply, socket} end end ``` The LiveView then receives this event using [`Phoenix.LiveView.handle_info/2`](Phoenix.LiveView.html#c:handle_info/2): ``` defmodule BoardView do ... def handle_info({:updated_card, card}, socket) do # update the list of cards in the socket {:noreply, updated_socket} end end ``` Because the list of cards in the parent socket was updated, the parent LiveView will be re-rendered, sending the updated card to the component. So in the end, the component does get the updated card, but always driven from the parent. Alternatively, instead of having the component send a message directly to the parent view, the component could broadcast the update using [`Phoenix.PubSub`](../phoenix_pubsub/2.1.3/Phoenix.PubSub.html). Such as: ``` defmodule CardComponent do ... def handle_event("update_title", %{"title" => title}, socket) do message = {:updated_card, %{socket.assigns.card | title: title}} Phoenix.PubSub.broadcast(MyApp.PubSub, board_topic(socket), message) {:noreply, socket} end defp board_topic(socket) do "board:" <> socket.assigns.board_id end end ``` As long as the parent LiveView subscribes to the `board:<ID>` topic, it will receive updates. The advantage of using PubSub is that we get distributed updates out of the box. Now, if any user connected to the board changes a card, all other users will see the change. ### [](Phoenix.LiveComponent.html#module-livecomponent-as-the-source-of-truth)LiveComponent as the source of truth If each card LiveComponent is the source of truth, then the board LiveView must no longer fetch the card structs from the database. Instead, the board LiveView must only fetch the card ids, then render each component only by passing an ID: ``` <.live_component :for={card_id <- @card_ids} module={CardComponent} id={card_id} board_id={@id} /> ``` Now, each CardComponent will load its own card. Of course, doing so per card could be expensive and lead to N queries, where N is the number of cards, so we can use the [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) callback to make it efficient. Once the card components are started, they can each manage their own card, without concerning themselves with the parent LiveView. However, note that components do not have a [`Phoenix.LiveView.handle_info/2`](Phoenix.LiveView.html#c:handle_info/2) callback. Therefore, if you want to track distributed changes on a card, you must have the parent LiveView receive those events and redirect them to the appropriate card. For example, assuming card updates are sent to the "board:ID" topic, and that the board LiveView is subscribed to said topic, one could do: ``` def handle_info({:updated_card, card}, socket) do send_update CardComponent, id: card.id, board_id: socket.assigns.id {:noreply, socket} end ``` With [`Phoenix.LiveView.send_update/3`](Phoenix.LiveView.html#send_update/3), the `CardComponent` given by `id` will be invoked, triggering the update or update\_many callback, which will load the most up to date data from the database. ### [](Phoenix.LiveComponent.html#module-unifying-liveview-and-livecomponent-communication)Unifying LiveView and LiveComponent communication In the examples above, we have used [`send/2`](../elixir/Kernel.html#send/2) to communicate with LiveView and `send_update/2` to communicate with components. This introduces a problem: what if you have a component that may be mounted both inside a LiveView or another component? Given each uses a different API for exchanging data, this may seem tricky at first, but an elegant solution is to use anonymous functions as callbacks. Let's see an example. In the sections above, we wrote the following code in our `CardComponent`: ``` def handle_event("update_title", %{"title" => title}, socket) do send self(), {:updated_card, %{socket.assigns.card | title: title}} {:noreply, socket} end ``` The issue with this code is that, if CardComponent is mounted inside another component, it will still message the LiveView. Not only that, this code may be hard to maintain because the message sent by the component is defined far away from the LiveView that will receive it. Instead let's define a callback that will be invoked by CardComponent: ``` def handle_event("update_title", %{"title" => title}, socket) do socket.assigns.on_card_update.(%{socket.assigns.card | title: title}) {:noreply, socket} end ``` And now when initializing the CardComponent from a LiveView, we may write: ``` <.live_component module={CardComponent} card={card} id={card.id} board_id={@id} on_card_update={fn card -> send(self(), {:updated_card, card}) end} /> ``` If initializing it inside another component, one may write: ``` <.live_component module={CardComponent} card={card} id={card.id} board_id={@id} on_card_update={fn card -> send_update(@myself, card: card) end} /> ``` The major benefit in both cases is that the parent has explicit control over the messages it will receive. ## [](Phoenix.LiveComponent.html#module-slots)Slots LiveComponent can also receive slots, in the same way as a [`Phoenix.Component`](Phoenix.Component.html): ``` <.live_component module={MyComponent} id={@data.id} > <div>Inner content here</div> </.live_component> ``` If the LiveComponent defines an [`update/2`](Phoenix.LiveComponent.html#c:update/2), be sure that the socket it returns includes the `:inner_block` assign it received. See [the docs](Phoenix.Component.html#module-slots.md) for [`Phoenix.Component`](Phoenix.Component.html) for more information. ## [](Phoenix.LiveComponent.html#module-live-patches-and-live-redirects)Live patches and live redirects A template rendered inside a component can use `<.link patch={...}>` and `<.link navigate={...}>`. Patches are always handled by the parent LiveView, as components do not provide `handle_params`. ## [](Phoenix.LiveComponent.html#module-cost-of-live-components)Cost of live components The internal infrastructure LiveView uses to keep track of live components is very lightweight. However, be aware that in order to provide change tracking and to send diffs over the wire, all of the components' assigns are kept in memory - exactly as it is done in LiveViews themselves. Therefore it is your responsibility to keep only the assigns necessary in each component. For example, avoid passing all of LiveView's assigns when rendering a component: ``` <.live_component module={MyComponent} {assigns} /> ``` Instead pass only the keys that you need: ``` <.live_component module={MyComponent} user={@user} org={@org} /> ``` Luckily, because LiveViews and LiveComponents are in the same process, they share the data structure representations in memory. For example, in the code above, the view and the component will share the same copies of the `@user` and `@org` assigns. You should also avoid using live components to provide abstract DOM components. As a guideline, a good LiveComponent encapsulates application concerns and not DOM functionality. For example, if you have a page that shows products for sale, you can encapsulate the rendering of each of those products in a component. This component may have many buttons and events within it. On the opposite side, do not write a component that is simply encapsulating generic DOM components. For instance, do not do this: ``` defmodule MyButton do use Phoenix.LiveComponent def render(assigns) do ~H""" <button class="css-framework-class" phx-click="click"> {@text} </button> """ end def handle_event("click", _, socket) do _ = socket.assigns.on_click.() {:noreply, socket} end end ``` Instead, it is much simpler to create a function component: ``` def my_button(%{text: _, click: _} = assigns) do ~H""" <button class="css-framework-class" phx-click={@click}> {@text} </button> """ end ``` If you keep components mostly as an application concern with only the necessary assigns, it is unlikely you will run into issues related to live components. ## [](Phoenix.LiveComponent.html#module-limitations)Limitations Live Components require a single HTML tag at the root. It is not possible to have components that render only text or multiple tags. # [](Phoenix.LiveComponent.html#summary)Summary ## [Callbacks](Phoenix.LiveComponent.html#callbacks) [handle\_async(name, async\_fun\_result, socket)](Phoenix.LiveComponent.html#c:handle_async/3) [handle\_event(event, unsigned\_params, socket)](Phoenix.LiveComponent.html#c:handle_event/3) [mount(socket)](Phoenix.LiveComponent.html#c:mount/1) [render(assigns)](Phoenix.LiveComponent.html#c:render/1) [update(assigns, socket)](Phoenix.LiveComponent.html#c:update/2) [update\_many(list)](Phoenix.LiveComponent.html#c:update_many/1) ## [Functions](Phoenix.LiveComponent.html#functions) [\_\_using\_\_(opts \\\\ \[\])](Phoenix.LiveComponent.html#__using__/1) Uses LiveComponent in the current module. # [](Phoenix.LiveComponent.html#callbacks)Callbacks [](Phoenix.LiveComponent.html#c:handle_async/3) # handle\_async(name, async\_fun\_result, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L606)(optional) ``` @callback handle_async( name :: term(), async_fun_result :: {:ok, term()} | {:exit, term()}, socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()} ``` [](Phoenix.LiveComponent.html#c:handle_event/3) # handle\_event(event, unsigned\_params, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L599)(optional) ``` @callback handle_event( event :: binary(), unsigned_params :: Phoenix.LiveView.unsigned_params(), socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()} | {:reply, map(), Phoenix.LiveView.Socket.t()} ``` [](Phoenix.LiveComponent.html#c:mount/1) # mount(socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L590)(optional) ``` @callback mount(socket :: Phoenix.LiveView.Socket.t()) :: {:ok, Phoenix.LiveView.Socket.t()} | {:ok, Phoenix.LiveView.Socket.t(), keyword()} ``` [](Phoenix.LiveComponent.html#c:render/1) # render(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L597)(optional) ``` @callback render(assigns :: Phoenix.LiveView.Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` [](Phoenix.LiveComponent.html#c:update/2) # update(assigns, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L593)(optional) ``` @callback update( assigns :: Phoenix.LiveView.Socket.assigns(), socket :: Phoenix.LiveView.Socket.t() ) :: {:ok, Phoenix.LiveView.Socket.t()} ``` [](Phoenix.LiveComponent.html#c:update_many/1) # update\_many(list) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L595)(optional) ``` @callback update_many([{Phoenix.LiveView.Socket.assigns(), Phoenix.LiveView.Socket.t()}]) :: [ Phoenix.LiveView.Socket.t() ] ``` # [](Phoenix.LiveComponent.html#functions)Functions [](Phoenix.LiveComponent.html#__using__/1) # \_\_using\_\_(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_component.ex#L576)(macro) Uses LiveComponent in the current module. ``` use Phoenix.LiveComponent ``` ## [](Phoenix.LiveComponent.html#__using__/1-options)Options - `:global_prefixes` - the global prefixes to use for components. See `Global Attributes` in [`Phoenix.Component`](Phoenix.Component.html) for more information. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.AsyncResult.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/async_result.ex#L1 "View Source") Phoenix.LiveView.AsyncResult (Phoenix LiveView v1.0.2) Provides a data structure for tracking the state of an async assign. See the `Async Operations` section of the [`Phoenix.LiveView`](Phoenix.LiveView.html) docs for more information. ## [](Phoenix.LiveView.AsyncResult.html#module-fields)Fields - `:ok?` - When true, indicates the `:result` has been set successfully at least once. - `:loading` - The current loading state - `:failed` - The current failed state - `:result` - The successful result of the async task # [](Phoenix.LiveView.AsyncResult.html#summary)Summary ## [Functions](Phoenix.LiveView.AsyncResult.html#functions) [failed(result, reason)](Phoenix.LiveView.AsyncResult.html#failed/2) Updates the failed state. [loading()](Phoenix.LiveView.AsyncResult.html#loading/0) Creates an async result in loading state. [loading(result)](Phoenix.LiveView.AsyncResult.html#loading/1) Updates the loading state. [loading(result, loading\_state)](Phoenix.LiveView.AsyncResult.html#loading/2) Updates the loading state of an existing `async_result`. [ok(value)](Phoenix.LiveView.AsyncResult.html#ok/1) Creates a successful result. [ok(result, value)](Phoenix.LiveView.AsyncResult.html#ok/2) Updates the successful result. # [](Phoenix.LiveView.AsyncResult.html#functions)Functions [](Phoenix.LiveView.AsyncResult.html#failed/2) # failed(result, reason) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/async_result.ex#L97) Updates the failed state. When failed, the loading state will be reset to `nil`. If the result was previously `ok?`, both `result` and `failed` will be set. ## [](Phoenix.LiveView.AsyncResult.html#failed/2-examples)Examples ``` iex> result = AsyncResult.loading() iex> result = AsyncResult.failed(result, {:exit, :boom}) iex> result.failed {:exit, :boom} iex> result.loading nil ``` [](Phoenix.LiveView.AsyncResult.html#loading/0) # loading() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/async_result.ex#L34) Creates an async result in loading state. ## [](Phoenix.LiveView.AsyncResult.html#loading/0-examples)Examples ``` iex> result = AsyncResult.loading() iex> result.loading true iex> result.ok? false ``` [](Phoenix.LiveView.AsyncResult.html#loading/1) # loading(result) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/async_result.ex#L53) Updates the loading state. When loading, the failed state will be reset to `nil`. ## [](Phoenix.LiveView.AsyncResult.html#loading/1-examples)Examples ``` iex> result = AsyncResult.loading(%{my: :loading_state}) iex> result.loading %{my: :loading_state} iex> result = AsyncResult.loading(result) iex> result.loading true ``` [](Phoenix.LiveView.AsyncResult.html#loading/2) # loading(result, loading\_state) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/async_result.ex#L76) Updates the loading state of an existing `async_result`. When loading, the failed state will be reset to `nil`. If the result was previously `ok?`, both `result` and `loading` will be set. ## [](Phoenix.LiveView.AsyncResult.html#loading/2-examples)Examples ``` iex> result = AsyncResult.loading() iex> result = AsyncResult.loading(result, %{my: :other_state}) iex> result.loading %{my: :other_state} ``` [](Phoenix.LiveView.AsyncResult.html#ok/1) # ok(value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/async_result.ex#L116) Creates a successful result. The `:ok?` field will also be set to `true` to indicate this result has completed successfully at least once, regardless of future state changes. ### [](Phoenix.LiveView.AsyncResult.html#ok/1-examples)Examples ``` iex> result = AsyncResult.ok("initial result") iex> result.ok? true iex> result.result "initial result" ``` [](Phoenix.LiveView.AsyncResult.html#ok/2) # ok(result, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/async_result.ex#L140) Updates the successful result. The `:ok?` field will also be set to `true` to indicate this result has completed successfully at least once, regardless of future state changes. When ok'd, the loading and failed state will be reset to `nil`. ## [](Phoenix.LiveView.AsyncResult.html#ok/2-examples)Examples ``` iex> result = AsyncResult.loading() iex> result = AsyncResult.ok(result, "completed") iex> result.ok? true iex> result.result "completed" iex> result.loading nil ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Component.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/engine.ex#L1 "View Source") Phoenix.LiveView.Component (Phoenix LiveView v1.0.2) The struct returned by components in .heex templates. This component is never meant to be output directly into the template. It should always be handled by the diffing algorithm. # [](Phoenix.LiveView.Component.html#summary)Summary ## [Types](Phoenix.LiveView.Component.html#types) [t()](Phoenix.LiveView.Component.html#t:t/0) # [](Phoenix.LiveView.Component.html#types)Types [](Phoenix.LiveView.Component.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/engine.ex#L12) ``` @type t() :: %Phoenix.LiveView.Component{ assigns: map(), component: module(), id: binary() } ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Comprehension.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/engine.ex#L54 "View Source") Phoenix.LiveView.Comprehension (Phoenix LiveView v1.0.2) The struct returned by for-comprehensions in .heex templates. See a description about its fields and use cases in [`Phoenix.LiveView.Engine`](Phoenix.LiveView.Engine.html) docs. # [](Phoenix.LiveView.Comprehension.html#summary)Summary ## [Types](Phoenix.LiveView.Comprehension.html#types) [t()](Phoenix.LiveView.Comprehension.html#t:t/0) # [](Phoenix.LiveView.Comprehension.html#types)Types [](Phoenix.LiveView.Comprehension.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/engine.ex#L64) ``` @type t() :: %Phoenix.LiveView.Comprehension{ dynamics: [ [ iodata() | Phoenix.LiveView.Rendered.t() | t() | Phoenix.LiveView.Component.t() ] ], fingerprint: integer(), static: [String.t()], stream: list() | nil } ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Controller.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/controller.ex#L1 "View Source") Phoenix.LiveView.Controller (Phoenix LiveView v1.0.2) Helpers for rendering LiveViews from a controller. # [](Phoenix.LiveView.Controller.html#summary)Summary ## [Functions](Phoenix.LiveView.Controller.html#functions) [live\_render(conn, view, opts \\\\ \[\])](Phoenix.LiveView.Controller.html#live_render/3) Renders a live view from a Plug request and sends an HTML response from within a controller. # [](Phoenix.LiveView.Controller.html#functions)Functions [](Phoenix.LiveView.Controller.html#live_render/3) # live\_render(conn, view, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/controller.ex#L38) Renders a live view from a Plug request and sends an HTML response from within a controller. It also automatically sets the `@live_module` assign with the value of the LiveView to be rendered. ## [](Phoenix.LiveView.Controller.html#live_render/3-options)Options See [`Phoenix.Component.live_render/3`](Phoenix.Component.html#live_render/3) for all supported options. ## [](Phoenix.LiveView.Controller.html#live_render/3-examples)Examples ``` defmodule ThermostatController do use MyAppWeb, :controller # "use MyAppWeb, :controller" should import Phoenix.LiveView.Controller. # If it does not, you can either import it there or uncomment the line below: # import Phoenix.LiveView.Controller def show(conn, %{"id" => thermostat_id}) do live_render(conn, ThermostatLive, session: %{ "thermostat_id" => thermostat_id, "current_user_id" => get_session(conn, :user_id) }) end end ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Engine.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/engine.ex#L170 "View Source") Phoenix.LiveView.Engine (Phoenix LiveView v1.0.2) An [`EEx`](../eex/EEx.html) template engine that tracks changes. This is often used by [`Phoenix.LiveView.TagEngine`](Phoenix.LiveView.TagEngine.html) which also adds HTML validation. In the documentation below, we will explain how it works internally. For user-facing documentation, see [`Phoenix.LiveView`](Phoenix.LiveView.html). ## [](Phoenix.LiveView.Engine.html#module-phoenix-liveview-rendered)Phoenix.LiveView.Rendered Whenever you render a live template, it returns a [`Phoenix.LiveView.Rendered`](Phoenix.LiveView.Rendered.html) structure. This structure has three fields: `:static`, `:dynamic` and `:fingerprint`. The `:static` field is a list of literal strings. This allows the Elixir compiler to optimize this list and avoid allocating its strings on every render. The `:dynamic` field contains a function that takes a boolean argument (see "Tracking changes" below), and returns a list of dynamic content. Each element in the list is either one of: 1. iodata - which is the dynamic content 2. nil - the dynamic content did not change 3. another [`Phoenix.LiveView.Rendered`](Phoenix.LiveView.Rendered.html) struct, see "Nesting and fingerprinting" below 4. a [`Phoenix.LiveView.Comprehension`](Phoenix.LiveView.Comprehension.html) struct, see "Comprehensions" below 5. a [`Phoenix.LiveView.Component`](Phoenix.LiveView.Component.html) struct, see "Component" below When you render a live template, you can convert the rendered structure to iodata by alternating the static and dynamic fields, always starting with a static entry followed by a dynamic entry. The last entry will always be static too. So the following structure: ``` %Phoenix.LiveView.Rendered{ static: ["foo", "bar", "baz"], dynamic: fn track_changes? -> ["left", "right"] end } ``` Results in the following content to be sent over the wire as iodata: ``` ["foo", "left", "bar", "right", "baz"] ``` This is also what calling [`Phoenix.HTML.Safe.to_iodata/1`](../phoenix_html/4.1.1/Phoenix.HTML.Safe.html#to_iodata/1) with a [`Phoenix.LiveView.Rendered`](Phoenix.LiveView.Rendered.html) structure returns. Of course, the benefit of live templates is exactly that you do not need to send both static and dynamic segments every time. So let's talk about tracking changes. ## [](Phoenix.LiveView.Engine.html#module-tracking-changes)Tracking changes By default, a live template does not track changes. Change tracking can be enabled by including a changed map in the assigns with the key `__changed__` and passing `true` to the dynamic parts. The map should contain the name of any changed field as key and the boolean true as value. If a field is not listed in `__changed__`, then it is always considered unchanged. If a field is unchanged and live believes a dynamic expression no longer needs to be computed, its value in the `dynamic` list will be `nil`. This information can be leveraged to avoid sending data to the client. ## [](Phoenix.LiveView.Engine.html#module-nesting-and-fingerprinting)Nesting and fingerprinting [`Phoenix.LiveView`](Phoenix.LiveView.html) also tracks changes across live templates. Therefore, if your view has this: ``` {render("form.html", assigns)} ``` Phoenix will be able to track what is static and dynamic across templates, as well as what changed. A rendered nested `live` template will appear in the `dynamic` list as another [`Phoenix.LiveView.Rendered`](Phoenix.LiveView.Rendered.html) structure, which must be handled recursively. However, because the rendering of live templates can be dynamic in itself, it is important to distinguish which live template was rendered. For example, imagine this code: ``` <%= if something?, do: render("one.html", assigns), else: render("other.html", assigns) %> ``` To solve this, all [`Phoenix.LiveView.Rendered`](Phoenix.LiveView.Rendered.html) structs also contain a fingerprint field that uniquely identifies it. If the fingerprints are equal, you have the same template, and therefore it is possible to only transmit its changes. ## [](Phoenix.LiveView.Engine.html#module-comprehensions)Comprehensions Another optimization done by live templates is to track comprehensions. If your code has this: ``` <%= for point <- @points do %> x: {point.x} y: {point.y} <% end %> ``` Instead of rendering all points with both static and dynamic parts, it returns a [`Phoenix.LiveView.Comprehension`](Phoenix.LiveView.Comprehension.html) struct with the static parts, that are shared across all points, and a list of dynamics to be interpolated inside the static parts. If `@points` is a list with `%{x: 1, y: 2}` and `%{x: 3, y: 4}`, the above expression would return: ``` %Phoenix.LiveView.Comprehension{ static: ["\n x: ", "\n y: ", "\n"], dynamics: [ ["1", "2"], ["3", "4"] ] } ``` This allows live templates to drastically optimize the data sent by comprehensions, as the static parts are emitted only once, regardless of the number of items. The list of dynamics is always a list of iodatas or components, as we don't perform change tracking inside the comprehensions themselves. Similarly, comprehensions do not have fingerprints because they are only optimized at the root, so conditional evaluation, as the one seen in rendering, is not possible. The only possible outcome for a dynamic field that returns a comprehension is `nil`. ## [](Phoenix.LiveView.Engine.html#module-components)Components Live also supports stateful components defined with [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html). Since they are stateful, they are always handled lazily by the diff algorithm. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.HTMLEngine.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/html_engine.ex#L1 "View Source") Phoenix.LiveView.HTMLEngine (Phoenix LiveView v1.0.2) The HTMLEngine that powers `.heex` templates and the `~H` sigil. It works by adding a HTML parsing and validation layer on top of [`Phoenix.LiveView.TagEngine`](Phoenix.LiveView.TagEngine.html). [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.HTMLFormatter.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/html_formatter.ex#L1 "View Source") Phoenix.LiveView.HTMLFormatter (Phoenix LiveView v1.0.2) Format HEEx templates from `.heex` files or `~H` sigils. This is a [`mix format`](../mix/Mix.Tasks.Format.html) [plugin](../mix/main/Mix.Tasks.Format.html#module-plugins). > Note: The HEEx HTML Formatter requires Elixir v1.13.4 or later. ## [](Phoenix.LiveView.HTMLFormatter.html#module-setup)Setup Add it as a plugin to your `.formatter.exs` file and make sure to put the `heex` extension in the `inputs` option. ``` [ plugins: [Phoenix.LiveView.HTMLFormatter], inputs: ["*.{heex,ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{heex,ex,exs}"], # ... ] ``` ### [](Phoenix.LiveView.HTMLFormatter.html#module-for-umbrella-projects)For umbrella projects In umbrella projects you must also change two files at the umbrella root, add `:phoenix_live_view` to your `deps` in the `mix.exs` file and add `plugins: [Phoenix.LiveView.HTMLFormatter]` in the `.formatter.exs` file. This is because the formatter does not attempt to load the dependencies of all children applications. ### [](Phoenix.LiveView.HTMLFormatter.html#module-editor-support)Editor support Most editors that support [`mix format`](../mix/Mix.Tasks.Format.html) integration should automatically format `.heex` and `~H` templates. Other editors may require custom integration or even provide additional functionality. Here are some reference posts: - [Formatting HEEx templates in VS Code](https://pragmaticstudio.com/tutorials/formatting-heex-templates-in-vscode) ## [](Phoenix.LiveView.HTMLFormatter.html#module-options)Options - `:line_length` - The Elixir formatter defaults to a maximum line length of 98 characters, which can be overwritten with the `:line_length` option in your `.formatter.exs` file. - `:heex_line_length` - change the line length only for the HEEx formatter. ``` [ # ...omitted heex_line_length: 300 ] ``` - `:migrate_eex_to_curly_interpolation` - Automatically migrate single expression `<%= ... %>` EEx expression to the curly braces one. Defaults to true. ## [](Phoenix.LiveView.HTMLFormatter.html#module-formatting)Formatting This formatter tries to be as consistent as possible with the Elixir formatter. Given HTML like this: ``` <section><h1> <b>{@user.name}</b></h1></section> ``` It will be formatted as: ``` <section> <h1><b>{@user.name}</b></h1> </section> ``` A block element will go to the next line, while inline elements will be kept in the current line as long as they fit within the configured line length. The following links list all block and inline elements. - [https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level\_elements#elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements#elements) - [https://developer.mozilla.org/en-US/docs/Web/HTML/Inline\_elements#list\_of\_inline\_elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements#list_of_inline_elements) It will also keep inline elements in their own lines if you intentionally write them this way: ``` <section> <h1> <b>{@user.name}</b> </h1> </section> ``` This formatter will place all attributes on their own lines when they do not all fit in the current line. Therefore this: ``` <section id="user-section-id" class="sm:focus:block flex w-full p-3" phx-click="send-event"> <p>Hi</p> </section> ``` Will be formatted to: ``` <section id="user-section-id" class="sm:focus:block flex w-full p-3" phx-click="send-event" > <p>Hi</p> </section> ``` This formatter **does not** format Elixir expressions with `do...end`. The content within it will be formatted accordingly though. Therefore, the given input: ``` <%= live_redirect( to: "/my/path", class: "my class" ) do %> My Link <% end %> ``` Will be formatted to ``` <%= live_redirect( to: "/my/path", class: "my class" ) do %> My Link <% end %> ``` Note that only the text `My Link` has been formatted. ### [](Phoenix.LiveView.HTMLFormatter.html#module-intentional-new-lines)Intentional new lines The formatter will keep intentional new lines. However, the formatter will always keep a maximum of one line break in case you have multiple ones: ``` <p> text text </p> ``` Will be formatted to: ``` <p> text text </p> ``` ### [](Phoenix.LiveView.HTMLFormatter.html#module-inline-elements)Inline elements We don't format inline elements when there is a text without whitespace before or after the element. Otherwise it would compromise what is rendered adding an extra whitespace. This is the list of inline elements: [https://developer.mozilla.org/en-US/docs/Web/HTML/Inline\_elements#list\_of\_inline\_elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements#list_of_inline_elements) ## [](Phoenix.LiveView.HTMLFormatter.html#module-skip-formatting)Skip formatting In case you don't want part of your HTML to be automatically formatted. You can use the special `phx-no-format` attribute so that the formatter will skip the element block. Note that this attribute will not be rendered. Therefore: ``` <.textarea phx-no-format>My content</.textarea> ``` Will be kept as is your code editor, but rendered as: ``` <textarea>My content</textarea> ``` # [](Phoenix.LiveView.HTMLFormatter.html#summary)Summary ## [Functions](Phoenix.LiveView.HTMLFormatter.html#functions) [is\_tag\_open(tag\_type)](Phoenix.LiveView.HTMLFormatter.html#is_tag_open/1) # [](Phoenix.LiveView.HTMLFormatter.html#functions)Functions [](Phoenix.LiveView.HTMLFormatter.html#is_tag_open/1) # is\_tag\_open(tag\_type) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/html_formatter.ex#L196)(macro) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.JS.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L1 "View Source") Phoenix.LiveView.JS (Phoenix LiveView v1.0.2) Provides commands for executing JavaScript utility operations on the client. JS commands support a variety of utility operations for common client-side needs, such as adding or removing CSS classes, setting or removing tag attributes, showing or hiding content, and transitioning in and out with animations. While these operations can be accomplished via client-side hooks, JS commands are DOM-patch aware, so operations applied by the JS APIs will stick to elements across patches from the server. In addition to purely client-side utilities, the JS commands include a rich `push` API, for extending the default `phx-` binding pushes with options to customize targets, loading states, and additional payload values. ## [](Phoenix.LiveView.JS.html#module-client-utility-commands)Client Utility Commands The following utilities are included: - `add_class` - Add classes to elements, with optional transitions - `remove_class` - Remove classes from elements, with optional transitions - `toggle_class` - Sets or removes classes from elements, with optional transitions - `set_attribute` - Set an attribute on elements - `remove_attribute` - Remove an attribute from elements - `toggle_attribute` - Sets or removes element attribute based on attribute presence. - `show` - Show elements, with optional transitions - `hide` - Hide elements, with optional transitions - `toggle` - Shows or hides elements based on visibility, with optional transitions - `transition` - Apply a temporary transition to elements for animations - `dispatch` - Dispatch a DOM event to elements For example, the following modal component can be shown or hidden on the client without a trip to the server: ``` alias Phoenix.LiveView.JS def hide_modal(js \\ %JS{}) do js |> JS.hide(transition: "fade-out", to: "#modal") |> JS.hide(transition: "fade-out-scale", to: "#modal-content") end def modal(assigns) do ~H""" <div id="modal" class="phx-modal" phx-remove={hide_modal()}> <div id="modal-content" class="phx-modal-content" phx-click-away={hide_modal()} phx-window-keydown={hide_modal()} phx-key="escape" > <button class="phx-modal-close" phx-click={hide_modal()}>✖</button> <p>{@text}</p> </div> </div> """ end ``` ## [](Phoenix.LiveView.JS.html#module-enhanced-push-events)Enhanced push events The [`push/1`](Phoenix.LiveView.JS.html#push/1) command allows you to extend the built-in pushed event handling when a `phx-` event is pushed to the server. For example, you may wish to target a specific component, specify additional payload values to include with the event, apply loading states to external elements, etc. For example, given this basic `phx-click` event: ``` <button phx-click="inc">+</button> ``` Imagine you need to target your current component, and apply a loading state to the parent container while the client awaits the server acknowledgement: ``` alias Phoenix.LiveView.JS ~H""" <button phx-click={JS.push("inc", loading: ".thermo", target: @myself)}>+</button> """ ``` Push commands also compose with all other utilities. For example, to add a class when pushing: ``` <button phx-click={ JS.push("inc", loading: ".thermo", target: @myself) |> JS.add_class("warmer", to: ".thermo") }>+</button> ``` Any `phx-value-*` attributes will also be included in the payload, their values will be overwritten by values given directly to [`push/1`](Phoenix.LiveView.JS.html#push/1). Any `phx-target` attribute will also be used, and overwritten. ``` <button phx-click={JS.push("inc", value: %{limit: 40})} phx-value-room="bedroom" phx-value-limit="this value will be 40" phx-target={@myself} >+</button> ``` ## [](Phoenix.LiveView.JS.html#module-custom-js-events-with-js-dispatch-1-and-window-addeventlistener)Custom JS events with `JS.dispatch/1` and `window.addEventListener` [`dispatch/1`](Phoenix.LiveView.JS.html#dispatch/1) can be used to dispatch custom JavaScript events to elements. For example, you can use `JS.dispatch("click", to: "#foo")`, to dispatch a click event to an element. This also means you can augment your elements with custom events, by using JavaScript's `window.addEventListener` and invoking them with [`dispatch/1`](Phoenix.LiveView.JS.html#dispatch/1). For example, imagine you want to provide a copy-to-clipboard functionality in your application. You can add a custom event for it: ``` window.addEventListener("my_app:clipcopy", (event) => { if ("clipboard" in navigator) { const text = event.target.textContent; navigator.clipboard.writeText(text); } else { alert("Sorry, your browser does not support clipboard copy."); } }); ``` Now you can have a button like this: ``` <button phx-click={JS.dispatch("my_app:clipcopy", to: "#element-with-text-to-copy")}> Copy content </button> ``` The combination of [`dispatch/1`](Phoenix.LiveView.JS.html#dispatch/1) with `window.addEventListener` is a powerful mechanism to increase the amount of actions you can trigger client-side from your LiveView code. You can also use `window.addEventListener` to listen to events pushed from the server. You can learn more in our [JS interoperability guide](js-interop.html). # [](Phoenix.LiveView.JS.html#summary)Summary ## [Types](Phoenix.LiveView.JS.html#types) [internal()](Phoenix.LiveView.JS.html#t:internal/0) [t()](Phoenix.LiveView.JS.html#t:t/0) ## [Functions](Phoenix.LiveView.JS.html#functions) [add\_class(names)](Phoenix.LiveView.JS.html#add_class/1) Adds classes to elements. [add\_class(js, names)](Phoenix.LiveView.JS.html#add_class/2) See [`add_class/1`](Phoenix.LiveView.JS.html#add_class/1). [add\_class(js, names, opts)](Phoenix.LiveView.JS.html#add_class/3) See [`add_class/1`](Phoenix.LiveView.JS.html#add_class/1). [concat(js1, js2)](Phoenix.LiveView.JS.html#concat/2) Combines two JS commands, appending the second to the first. [dispatch(js \\\\ %JS{}, event)](Phoenix.LiveView.JS.html#dispatch/2) Dispatches an event to the DOM. [dispatch(js, event, opts)](Phoenix.LiveView.JS.html#dispatch/3) See [`dispatch/2`](Phoenix.LiveView.JS.html#dispatch/2). [exec(attr)](Phoenix.LiveView.JS.html#exec/1) Executes JS commands located in an element's attribute. [exec(attr, opts)](Phoenix.LiveView.JS.html#exec/2) See [`exec/1`](Phoenix.LiveView.JS.html#exec/1). [exec(js, attr, opts)](Phoenix.LiveView.JS.html#exec/3) See [`exec/1`](Phoenix.LiveView.JS.html#exec/1). [focus(opts \\\\ \[\])](Phoenix.LiveView.JS.html#focus/1) Sends focus to a selector. [focus(js, opts)](Phoenix.LiveView.JS.html#focus/2) See [`focus/1`](Phoenix.LiveView.JS.html#focus/1). [focus\_first(opts \\\\ \[\])](Phoenix.LiveView.JS.html#focus_first/1) Sends focus to the first focusable child in selector. [focus\_first(js, opts)](Phoenix.LiveView.JS.html#focus_first/2) See [`focus_first/1`](Phoenix.LiveView.JS.html#focus_first/1). [hide(opts \\\\ \[\])](Phoenix.LiveView.JS.html#hide/1) Hides elements. [hide(js, opts)](Phoenix.LiveView.JS.html#hide/2) See [`hide/1`](Phoenix.LiveView.JS.html#hide/1). [navigate(href)](Phoenix.LiveView.JS.html#navigate/1) Sends a navigation event to the server and updates the browser's pushState history. [navigate(href, opts)](Phoenix.LiveView.JS.html#navigate/2) See [`navigate/1`](Phoenix.LiveView.JS.html#navigate/1). [navigate(js, href, opts)](Phoenix.LiveView.JS.html#navigate/3) See [`navigate/1`](Phoenix.LiveView.JS.html#navigate/1). [patch(href)](Phoenix.LiveView.JS.html#patch/1) Sends a patch event to the server and updates the browser's pushState history. [patch(href, opts)](Phoenix.LiveView.JS.html#patch/2) See [`patch/1`](Phoenix.LiveView.JS.html#patch/1). [patch(js, href, opts)](Phoenix.LiveView.JS.html#patch/3) See [`patch/1`](Phoenix.LiveView.JS.html#patch/1). [pop\_focus(js \\\\ %JS{})](Phoenix.LiveView.JS.html#pop_focus/1) Focuses the last pushed element. [push(event)](Phoenix.LiveView.JS.html#push/1) Pushes an event to the server. [push(event, opts)](Phoenix.LiveView.JS.html#push/2) See [`push/1`](Phoenix.LiveView.JS.html#push/1). [push(js, event, opts)](Phoenix.LiveView.JS.html#push/3) See [`push/1`](Phoenix.LiveView.JS.html#push/1). [push\_focus(opts \\\\ \[\])](Phoenix.LiveView.JS.html#push_focus/1) Pushes focus from the source element to be later popped. [push\_focus(js, opts)](Phoenix.LiveView.JS.html#push_focus/2) See [`push_focus/1`](Phoenix.LiveView.JS.html#push_focus/1). [remove\_attribute(attr)](Phoenix.LiveView.JS.html#remove_attribute/1) Removes an attribute from elements. [remove\_attribute(attr, opts)](Phoenix.LiveView.JS.html#remove_attribute/2) See [`remove_attribute/1`](Phoenix.LiveView.JS.html#remove_attribute/1). [remove\_attribute(js, attr, opts)](Phoenix.LiveView.JS.html#remove_attribute/3) See [`remove_attribute/1`](Phoenix.LiveView.JS.html#remove_attribute/1). [remove\_class(names)](Phoenix.LiveView.JS.html#remove_class/1) Removes classes from elements. [remove\_class(js, names)](Phoenix.LiveView.JS.html#remove_class/2) See [`remove_class/1`](Phoenix.LiveView.JS.html#remove_class/1). [remove\_class(js, names, opts)](Phoenix.LiveView.JS.html#remove_class/3) See [`remove_class/1`](Phoenix.LiveView.JS.html#remove_class/1). [set\_attribute(arg)](Phoenix.LiveView.JS.html#set_attribute/1) Sets an attribute on elements. [set\_attribute(js, opts)](Phoenix.LiveView.JS.html#set_attribute/2) See [`set_attribute/1`](Phoenix.LiveView.JS.html#set_attribute/1). [set\_attribute(js, arg, opts)](Phoenix.LiveView.JS.html#set_attribute/3) See [`set_attribute/1`](Phoenix.LiveView.JS.html#set_attribute/1). [show(opts \\\\ \[\])](Phoenix.LiveView.JS.html#show/1) Shows elements. [show(js, opts)](Phoenix.LiveView.JS.html#show/2) See [`show/1`](Phoenix.LiveView.JS.html#show/1). [toggle(opts \\\\ \[\])](Phoenix.LiveView.JS.html#toggle/1) Toggles element visibility. [toggle(js, opts)](Phoenix.LiveView.JS.html#toggle/2) See [`toggle/1`](Phoenix.LiveView.JS.html#toggle/1). [toggle\_attribute(arg)](Phoenix.LiveView.JS.html#toggle_attribute/1) Sets or removes element attribute based on attribute presence. [toggle\_attribute(js, opts)](Phoenix.LiveView.JS.html#toggle_attribute/2) See [`toggle_attribute/1`](Phoenix.LiveView.JS.html#toggle_attribute/1). [toggle\_attribute(js, arg, opts)](Phoenix.LiveView.JS.html#toggle_attribute/3) See [`toggle_attribute/1`](Phoenix.LiveView.JS.html#toggle_attribute/1). [toggle\_class(names)](Phoenix.LiveView.JS.html#toggle_class/1) Adds or removes element classes based on presence. [toggle\_class(js, names)](Phoenix.LiveView.JS.html#toggle_class/2) [toggle\_class(js, names, opts)](Phoenix.LiveView.JS.html#toggle_class/3) [transition(transition)](Phoenix.LiveView.JS.html#transition/1) Transitions elements. [transition(transition, opts)](Phoenix.LiveView.JS.html#transition/2) See [`transition/1`](Phoenix.LiveView.JS.html#transition/1). [transition(js, transition, opts)](Phoenix.LiveView.JS.html#transition/3) See [`transition/1`](Phoenix.LiveView.JS.html#transition/1). # [](Phoenix.LiveView.JS.html#types)Types [](Phoenix.LiveView.JS.html#t:internal/0) # internal() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L146) ``` @opaque internal() ``` [](Phoenix.LiveView.JS.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L147) ``` @type t() :: %Phoenix.LiveView.JS{ops: internal()} ``` # [](Phoenix.LiveView.JS.html#functions)Functions [](Phoenix.LiveView.JS.html#add_class/1) # add\_class(names) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L485) Adds classes to elements. - `names` - A string with one or more class names to add. ## [](Phoenix.LiveView.JS.html#add_class/1-options)Options - `:to` - An optional DOM selector to add classes to. Defaults to the interacted element. - `:transition` - A string of classes to apply before adding classes or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-0", "opacity-100"}` - `:time` - The time in milliseconds to apply the transition from `:transition`. Defaults to 200. - `:blocking` - A boolean flag to block the UI during the transition. Defaults `true`. ## [](Phoenix.LiveView.JS.html#add_class/1-examples)Examples ``` <div id="item">My Item</div> <button phx-click={JS.add_class("highlight underline", to: "#item")}> highlight! </button> ``` [](Phoenix.LiveView.JS.html#add_class/2) # add\_class(js, names) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L488) See [`add_class/1`](Phoenix.LiveView.JS.html#add_class/1). [](Phoenix.LiveView.JS.html#add_class/3) # add\_class(js, names, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L497) See [`add_class/1`](Phoenix.LiveView.JS.html#add_class/1). [](Phoenix.LiveView.JS.html#concat/2) # concat(js1, js2) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L961) Combines two JS commands, appending the second to the first. [](Phoenix.LiveView.JS.html#dispatch/2) # dispatch(js \\\\ %JS{}, event) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L242) Dispatches an event to the DOM. - `event` - The string event name to dispatch. *Note*: All events dispatched are of a type [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent), with the exception of `"click"`. For a `"click"`, a [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) is dispatched to properly simulate a UI click. For emitted `CustomEvent`'s, the event detail will contain a `dispatcher`, which references the DOM node that dispatched the JS event to the target element. ## [](Phoenix.LiveView.JS.html#dispatch/2-options)Options - `:to` - An optional DOM selector to dispatch the event to. Defaults to the interacted element. - `:detail` - An optional detail map to dispatch along with the client event. The details will be available in the `event.detail` attribute for event listeners. - `:bubbles` – A boolean flag to bubble the event or not. Defaults to `true`. ## [](Phoenix.LiveView.JS.html#dispatch/2-examples)Examples ``` window.addEventListener("click", e => console.log("clicked!", e.detail)) ``` ``` <button phx-click={JS.dispatch("click", to: ".nav")}>Click me!</button> ``` [](Phoenix.LiveView.JS.html#dispatch/3) # dispatch(js, event, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L247) See [`dispatch/2`](Phoenix.LiveView.JS.html#dispatch/2). [](Phoenix.LiveView.JS.html#exec/1) # exec(attr) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L939) Executes JS commands located in an element's attribute. - `attr` - The string attribute where the JS command is specified ## [](Phoenix.LiveView.JS.html#exec/1-options)Options - `:to` - An optional DOM selector to fetch the attribute from. Defaults to the current element. ## [](Phoenix.LiveView.JS.html#exec/1-examples)Examples ``` <div id="modal" phx-remove={JS.hide("#modal")}>...</div> <button phx-click={JS.exec("phx-remove", to: "#modal")}>close</button> ``` [](Phoenix.LiveView.JS.html#exec/2) # exec(attr, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L944) See [`exec/1`](Phoenix.LiveView.JS.html#exec/1). [](Phoenix.LiveView.JS.html#exec/3) # exec(js, attr, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L953) See [`exec/1`](Phoenix.LiveView.JS.html#exec/1). [](Phoenix.LiveView.JS.html#focus/1) # focus(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L796) Sends focus to a selector. ## [](Phoenix.LiveView.JS.html#focus/1-options)Options - `:to` - An optional DOM selector to send focus to. Defaults to the current element. ## [](Phoenix.LiveView.JS.html#focus/1-examples)Examples ``` JS.focus(to: "main") ``` [](Phoenix.LiveView.JS.html#focus/2) # focus(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L801) See [`focus/1`](Phoenix.LiveView.JS.html#focus/1). [](Phoenix.LiveView.JS.html#focus_first/1) # focus\_first(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L818) Sends focus to the first focusable child in selector. ## [](Phoenix.LiveView.JS.html#focus_first/1-options)Options - `:to` - An optional DOM selector to focus. Defaults to the current element. ## [](Phoenix.LiveView.JS.html#focus_first/1-examples)Examples ``` JS.focus_first(to: "#modal") ``` [](Phoenix.LiveView.JS.html#focus_first/2) # focus\_first(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L823) See [`focus_first/1`](Phoenix.LiveView.JS.html#focus_first/1). [](Phoenix.LiveView.JS.html#hide/1) # hide(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L441) Hides elements. *Note*: Only targets elements that are visible, meaning they have a height and/or width greater than zero. ## [](Phoenix.LiveView.JS.html#hide/1-options)Options - `:to` - An optional DOM selector to hide. Defaults to the interacted element. - `:transition` - A string of classes to apply before hiding or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-100", "opacity-0"}` - `:time` - The time in milliseconds to apply the transition from `:transition`. Defaults to 200. - `:blocking` - A boolean flag to block the UI during the transition. Defaults `true`. During the process, the following events will be dispatched to the hidden elements: - When the action is triggered on the client, `phx:hide-start` is dispatched. - After the time specified by `:time`, `phx:hide-end` is dispatched. ## [](Phoenix.LiveView.JS.html#hide/1-examples)Examples ``` <div id="item">My Item</div> <button phx-click={JS.hide(to: "#item")}> hide! </button> <button phx-click={JS.hide(to: "#item", transition: "fade-out-scale")}> hide fancy! </button> ``` [](Phoenix.LiveView.JS.html#hide/2) # hide(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L446) See [`hide/1`](Phoenix.LiveView.JS.html#hide/1). [](Phoenix.LiveView.JS.html#navigate/1) # navigate(href) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L873) Sends a navigation event to the server and updates the browser's pushState history. ## [](Phoenix.LiveView.JS.html#navigate/1-options)Options - `:replace` - Whether to replace the browser's pushState history. Defaults to `false`. ## [](Phoenix.LiveView.JS.html#navigate/1-examples)Examples ``` JS.navigate("/my-path") ``` [](Phoenix.LiveView.JS.html#navigate/2) # navigate(href, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L878) See [`navigate/1`](Phoenix.LiveView.JS.html#navigate/1). [](Phoenix.LiveView.JS.html#navigate/3) # navigate(js, href, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L887) See [`navigate/1`](Phoenix.LiveView.JS.html#navigate/1). [](Phoenix.LiveView.JS.html#patch/1) # patch(href) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L903) Sends a patch event to the server and updates the browser's pushState history. ## [](Phoenix.LiveView.JS.html#patch/1-options)Options - `:replace` - Whether to replace the browser's pushState history. Defaults to `false`. ## [](Phoenix.LiveView.JS.html#patch/1-examples)Examples ``` JS.patch("/my-path") ``` [](Phoenix.LiveView.JS.html#patch/2) # patch(href, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L908) See [`patch/1`](Phoenix.LiveView.JS.html#patch/1). [](Phoenix.LiveView.JS.html#patch/3) # patch(js, href, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L917) See [`patch/1`](Phoenix.LiveView.JS.html#patch/1). [](Phoenix.LiveView.JS.html#pop_focus/1) # pop\_focus(js \\\\ %JS{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L858) Focuses the last pushed element. ## [](Phoenix.LiveView.JS.html#pop_focus/1-examples)Examples ``` JS.pop_focus() ``` [](Phoenix.LiveView.JS.html#push/1) # push(event) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L184) Pushes an event to the server. - `event` - The string event name to push. ## [](Phoenix.LiveView.JS.html#push/1-options)Options - `:target` - A selector or component ID to push to. This value will overwrite any `phx-target` attribute present on the element. - `:loading` - A selector to apply the phx loading classes to, such as `phx-click-loading` in case the event was triggered by `phx-click`. The element will be locked from server updates until the push is acknowledged by the server. - `:page_loading` - Boolean to trigger the phx:page-loading-start and phx:page-loading-stop events for this push. Defaults to `false`. - `:value` - A map of values to send to the server. These values will be merged over any `phx-value-*` attributes that are present on the element. All keys will be treated as strings when merging. ## [](Phoenix.LiveView.JS.html#push/1-examples)Examples ``` <button phx-click={JS.push("clicked")}>click me!</button> <button phx-click={JS.push("clicked", value: %{id: @id})}>click me!</button> <button phx-click={JS.push("clicked", page_loading: true)}>click me!</button> ``` [](Phoenix.LiveView.JS.html#push/2) # push(event, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L189) See [`push/1`](Phoenix.LiveView.JS.html#push/1). [](Phoenix.LiveView.JS.html#push/3) # push(js, event, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L198) See [`push/1`](Phoenix.LiveView.JS.html#push/1). [](Phoenix.LiveView.JS.html#push_focus/1) # push\_focus(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L841) Pushes focus from the source element to be later popped. ## [](Phoenix.LiveView.JS.html#push_focus/1-options)Options - `:to` - An optional DOM selector to push focus to. Defaults to the current element. ## [](Phoenix.LiveView.JS.html#push_focus/1-examples)Examples ``` JS.push_focus() JS.push_focus(to: "#my-button") ``` [](Phoenix.LiveView.JS.html#push_focus/2) # push\_focus(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L846) See [`push_focus/1`](Phoenix.LiveView.JS.html#push_focus/1). [](Phoenix.LiveView.JS.html#remove_attribute/1) # remove\_attribute(attr) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L718) Removes an attribute from elements. - `attr` - The string attribute name to remove. ## [](Phoenix.LiveView.JS.html#remove_attribute/1-options)Options - `:to` - An optional DOM selector to remove attributes from. Defaults to the interacted element. ## [](Phoenix.LiveView.JS.html#remove_attribute/1-examples)Examples ``` <button phx-click={JS.remove_attribute("aria-expanded", to: "#dropdown")}> hide </button> ``` [](Phoenix.LiveView.JS.html#remove_attribute/2) # remove\_attribute(attr, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L721) See [`remove_attribute/1`](Phoenix.LiveView.JS.html#remove_attribute/1). [](Phoenix.LiveView.JS.html#remove_attribute/3) # remove\_attribute(js, attr, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L727) See [`remove_attribute/1`](Phoenix.LiveView.JS.html#remove_attribute/1). [](Phoenix.LiveView.JS.html#remove_class/1) # remove\_class(names) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L585) Removes classes from elements. - `names` - A string with one or more class names to remove. ## [](Phoenix.LiveView.JS.html#remove_class/1-options)Options - `:to` - An optional DOM selector to remove classes from. Defaults to the interacted element. - `:transition` - A string of classes to apply before removing classes or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-0", "opacity-100"}` - `:time` - The time in milliseconds to apply the transition from `:transition`. Defaults to 200. - `:blocking` - A boolean flag to block the UI during the transition. Defaults `true`. ## [](Phoenix.LiveView.JS.html#remove_class/1-examples)Examples ``` <div id="item">My Item</div> <button phx-click={JS.remove_class("highlight underline", to: "#item")}> remove highlight! </button> ``` [](Phoenix.LiveView.JS.html#remove_class/2) # remove\_class(js, names) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L588) See [`remove_class/1`](Phoenix.LiveView.JS.html#remove_class/1). [](Phoenix.LiveView.JS.html#remove_class/3) # remove\_class(js, names, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L597) See [`remove_class/1`](Phoenix.LiveView.JS.html#remove_class/1). [](Phoenix.LiveView.JS.html#set_attribute/1) # set\_attribute(arg) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L686) Sets an attribute on elements. Accepts a tuple containing the string attribute name/value pair. ## [](Phoenix.LiveView.JS.html#set_attribute/1-options)Options - `:to` - An optional DOM selector to add attributes to. Defaults to the interacted element. ## [](Phoenix.LiveView.JS.html#set_attribute/1-examples)Examples ``` <button phx-click={JS.set_attribute({"aria-expanded", "true"}, to: "#dropdown")}> show </button> ``` [](Phoenix.LiveView.JS.html#set_attribute/2) # set\_attribute(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L689) See [`set_attribute/1`](Phoenix.LiveView.JS.html#set_attribute/1). [](Phoenix.LiveView.JS.html#set_attribute/3) # set\_attribute(js, arg, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L695) See [`set_attribute/1`](Phoenix.LiveView.JS.html#set_attribute/1). [](Phoenix.LiveView.JS.html#show/1) # show(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L386) Shows elements. *Note*: Only targets elements that are hidden, meaning they have a height and/or width equal to zero. ## [](Phoenix.LiveView.JS.html#show/1-options)Options - `:to` - An optional DOM selector to show. Defaults to the interacted element. - `:transition` - A string of classes to apply before showing or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-0", "opacity-100"}` - `:time` - The time in milliseconds to apply the transition from `:transition`. Defaults to 200. - `:blocking` - A boolean flag to block the UI during the transition. Defaults `true`. - `:display` - An optional display value to set when showing. Defaults to `"block"`. During the process, the following events will be dispatched to the shown elements: - When the action is triggered on the client, `phx:show-start` is dispatched. - After the time specified by `:time`, `phx:show-end` is dispatched. ## [](Phoenix.LiveView.JS.html#show/1-examples)Examples ``` <div id="item">My Item</div> <button phx-click={JS.show(to: "#item")}> show! </button> <button phx-click={JS.show(to: "#item", transition: "fade-in-scale")}> show fancy! </button> ``` [](Phoenix.LiveView.JS.html#show/2) # show(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L391) See [`show/1`](Phoenix.LiveView.JS.html#show/1). [](Phoenix.LiveView.JS.html#toggle/1) # toggle(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L328) Toggles element visibility. ## [](Phoenix.LiveView.JS.html#toggle/1-options)Options - `:to` - An optional DOM selector to toggle. Defaults to the interacted element. - `:in` - A string of classes to apply when toggling in, or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-0", "opacity-100"}` - `:out` - A string of classes to apply when toggling out, or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-100", "opacity-0"}` - `:time` - The time in milliseconds to apply the transition `:in` and `:out` classes. Defaults to 200. - `:display` - An optional display value to set when toggling in. Defaults to `"block"`. - `:blocking` - A boolean flag to block the UI during the transition. Defaults `true`. When the toggle is complete on the client, a `phx:show-start` or `phx:hide-start`, and `phx:show-end` or `phx:hide-end` event will be dispatched to the toggled elements. ## [](Phoenix.LiveView.JS.html#toggle/1-examples)Examples ``` <div id="item">My Item</div> <button phx-click={JS.toggle(to: "#item")}> toggle item! </button> <button phx-click={JS.toggle(to: "#item", in: "fade-in-scale", out: "fade-out-scale")}> toggle fancy! </button> ``` [](Phoenix.LiveView.JS.html#toggle/2) # toggle(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L333) See [`toggle/1`](Phoenix.LiveView.JS.html#toggle/1). [](Phoenix.LiveView.JS.html#toggle_attribute/1) # toggle\_attribute(arg) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L758) Sets or removes element attribute based on attribute presence. Accepts a two or three-element tuple: - `{attr, val}` - Sets the attribute to the given value or removes it - `{attr, val1, val2}` - Toggles the attribute between `val1` and `val2` ## [](Phoenix.LiveView.JS.html#toggle_attribute/1-options)Options - `:to` - An optional DOM selector to set or remove attributes from. Defaults to the interacted element. ## [](Phoenix.LiveView.JS.html#toggle_attribute/1-examples)Examples ``` <button phx-click={JS.toggle_attribute({"aria-expanded", "true", "false"}, to: "#dropdown")}> toggle </button> <button phx-click={JS.toggle_attribute({"open", "true"}, to: "#dialog")}> toggle </button> ``` [](Phoenix.LiveView.JS.html#toggle_attribute/2) # toggle\_attribute(js, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L762) See [`toggle_attribute/1`](Phoenix.LiveView.JS.html#toggle_attribute/1). [](Phoenix.LiveView.JS.html#toggle_attribute/3) # toggle\_attribute(js, arg, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L774) See [`toggle_attribute/1`](Phoenix.LiveView.JS.html#toggle_attribute/1). [](Phoenix.LiveView.JS.html#toggle_class/1) # toggle\_class(names) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L536) Adds or removes element classes based on presence. - `names` - A string with one or more class names to toggle. ## [](Phoenix.LiveView.JS.html#toggle_class/1-options)Options - `:to` - An optional DOM selector to target. Defaults to the interacted element. - `:transition` - A string of classes to apply before adding classes or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-0", "opacity-100"}` - `:time` - The time in milliseconds to apply the transition from `:transition`. Defaults to 200. - `:blocking` - A boolean flag to block the UI during the transition. Defaults `true`. ## [](Phoenix.LiveView.JS.html#toggle_class/1-examples)Examples ``` <div id="item">My Item</div> <button phx-click={JS.toggle_class("active", to: "#item")}> toggle active! </button> ``` [](Phoenix.LiveView.JS.html#toggle_class/2) # toggle\_class(js, names) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L538) [](Phoenix.LiveView.JS.html#toggle_class/3) # toggle\_class(js, names, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L546) [](Phoenix.LiveView.JS.html#transition/1) # transition(transition) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L640) Transitions elements. - `transition` - A string of classes to apply before removing classes or a 3-tuple containing the transition class, the class to apply to start the transition, and the ending transition class, such as: `{"ease-out duration-300", "opacity-0", "opacity-100"}` Transitions are useful for temporarily adding an animation class to elements, such as for highlighting content changes. ## [](Phoenix.LiveView.JS.html#transition/1-options)Options - `:to` - An optional DOM selector to apply transitions to. Defaults to the interacted element. - `:time` - The time in milliseconds to apply the transition from `:transition`. Defaults to 200. - `:blocking` - A boolean flag to block the UI during the transition. Defaults `true`. ## [](Phoenix.LiveView.JS.html#transition/1-examples)Examples ``` <div id="item">My Item</div> <button phx-click={JS.transition("shake", to: "#item")}>Shake!</button> <div phx-mounted={JS.transition({"ease-out duration-300", "opacity-0", "opacity-100"}, time: 300)}> duration-300 milliseconds matches time: 300 milliseconds <div> ``` [](Phoenix.LiveView.JS.html#transition/2) # transition(transition, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L645) See [`transition/1`](Phoenix.LiveView.JS.html#transition/1). [](Phoenix.LiveView.JS.html#transition/3) # transition(js, transition, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/js.ex#L655) See [`transition/1`](Phoenix.LiveView.JS.html#transition/1). [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Logger.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/logger.ex#L1 "View Source") Phoenix.LiveView.Logger (Phoenix LiveView v1.0.2) Instrumenter to handle logging of [`Phoenix.LiveView`](Phoenix.LiveView.html) and [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) life-cycle events. ## [](Phoenix.LiveView.Logger.html#module-installation)Installation The logger is installed automatically when Live View starts. By default, the log level is set to `:debug`. ## [](Phoenix.LiveView.Logger.html#module-module-configuration)Module configuration The log level can be overridden for an individual Live View module: ``` use Phoenix.LiveView, log: :debug ``` To disable logging for an individual Live View module: ``` use Phoenix.LiveView, log: false ``` ## [](Phoenix.LiveView.Logger.html#module-telemetry)Telemetry The following [`Phoenix.LiveView`](Phoenix.LiveView.html) and [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) events are logged: - `[:phoenix, :live_view, :mount, :start]` - `[:phoenix, :live_view, :mount, :stop]` - `[:phoenix, :live_view, :handle_params, :start]` - `[:phoenix, :live_view, :handle_params, :stop]` - `[:phoenix, :live_view, :handle_event, :start]` - `[:phoenix, :live_view, :handle_event, :stop]` - `[:phoenix, :live_component, :handle_event, :start]` - `[:phoenix, :live_component, :handle_event, :stop]` See the [Telemetry](telemetry.html) guide for more information. ## [](Phoenix.LiveView.Logger.html#module-parameter-filtering)Parameter filtering If enabled, [`Phoenix.LiveView.Logger`](Phoenix.LiveView.Logger.html) will filter parameters based on the configuration of [`Phoenix.Logger`](../phoenix/1.7.18/Phoenix.Logger.html). [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.ReloadError.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/static.ex#L1 "View Source") Phoenix.LiveView.ReloadError exception (Phoenix LiveView v1.0.2) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Rendered.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/engine.ex#L119 "View Source") Phoenix.LiveView.Rendered (Phoenix LiveView v1.0.2) The struct returned by .heex templates. See a description about its fields and use cases in [`Phoenix.LiveView.Engine`](Phoenix.LiveView.Engine.html) docs. # [](Phoenix.LiveView.Rendered.html#summary)Summary ## [Types](Phoenix.LiveView.Rendered.html#types) [t()](Phoenix.LiveView.Rendered.html#t:t/0) # [](Phoenix.LiveView.Rendered.html#types)Types [](Phoenix.LiveView.Rendered.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/engine.ex#L129) ``` @type t() :: %Phoenix.LiveView.Rendered{ caller: :not_available | {module(), function :: {atom(), non_neg_integer()}, file :: String.t(), line :: pos_integer()}, dynamic: (boolean() -> [ nil | iodata() | t() | Phoenix.LiveView.Comprehension.t() | Phoenix.LiveView.Component.t() ]), fingerprint: integer(), root: nil | true | false, static: [String.t()] } ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Router.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/router.ex#L1 "View Source") Phoenix.LiveView.Router (Phoenix LiveView v1.0.2) Provides LiveView routing for Phoenix routers. # [](Phoenix.LiveView.Router.html#summary)Summary ## [Functions](Phoenix.LiveView.Router.html#functions) [fetch\_live\_flash(conn, opts \\\\ \[\])](Phoenix.LiveView.Router.html#fetch_live_flash/2) Fetches the LiveView and merges with the controller flash. [live(path, live\_view, action \\\\ nil, opts \\\\ \[\])](Phoenix.LiveView.Router.html#live/4) Defines a LiveView route. [live\_session(name, opts \\\\ \[\], list)](Phoenix.LiveView.Router.html#live_session/3) Defines a live session for live redirects within a group of live routes. # [](Phoenix.LiveView.Router.html#functions)Functions [](Phoenix.LiveView.Router.html#fetch_live_flash/2) # fetch\_live\_flash(conn, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/router.ex#L355) Fetches the LiveView and merges with the controller flash. Replaces the default `:fetch_flash` plug used by [`Phoenix.Router`](../phoenix/1.7.18/Phoenix.Router.html). ## [](Phoenix.LiveView.Router.html#fetch_live_flash/2-examples)Examples ``` defmodule MyAppWeb.Router do use LiveGenWeb, :router import Phoenix.LiveView.Router pipeline :browser do ... plug :fetch_live_flash end ... end ``` [](Phoenix.LiveView.Router.html#live/4) # live(path, live\_view, action \\\\ nil, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/router.ex#L107)(macro) Defines a LiveView route. A LiveView can be routed to by using the `live` macro with a path and the name of the LiveView: ``` live "/thermostat", ThermostatLive ``` By default, you can generate a route to this LiveView by using the `live_path` helper: ``` live_path(@socket, ThermostatLive) ``` #### HTTP requests The HTTP request method that a route defined by the [`live/4`](Phoenix.LiveView.Router.html#live/4) macro responds to is `GET`. ## [](Phoenix.LiveView.Router.html#live/4-actions-and-live-navigation)Actions and live navigation It is common for a LiveView to have multiple states and multiple URLs. For example, you can have a single LiveView that lists all articles on your web app. For each article there is an "Edit" button which, when pressed, opens up a modal on the same page to edit the article. It is a best practice to use live navigation in those cases, so when you click edit, the URL changes to "/articles/1/edit", even though you are still within the same LiveView. Similarly, you may also want to show a "New" button, which opens up the modal to create new entries, and you want this to be reflected in the URL as "/articles/new". In order to make it easier to recognize the current "action" your LiveView is on, you can pass the action option when defining LiveViews too: ``` live "/articles", ArticleLive.Index, :index live "/articles/new", ArticleLive.Index, :new live "/articles/:id/edit", ArticleLive.Index, :edit ``` When an action is given, the generated route helpers are named after the LiveView itself (in the same way as for a controller). For the example above, we will have: ``` article_index_path(@socket, :index) article_index_path(@socket, :new) article_index_path(@socket, :edit, 123) ``` The current action will always be available inside the LiveView as the `@live_action` assign, that can be used to render a LiveComponent: ``` <.live_component :if={@live_action == :new} module={MyAppWeb.ArticleLive.FormComponent} id="form" /> ``` Or can be used to show or hide parts of the template: ``` {if @live_action == :edit, do: render("form.html", user: @user)} ``` Note that `@live_action` will be `nil` if no action is given on the route definition. ## [](Phoenix.LiveView.Router.html#live/4-options)Options - `:container` - an optional tuple for the HTML tag and DOM attributes to be used for the LiveView container. For example: `{:li, style: "color: blue;"}`. See [`Phoenix.Component.live_render/3`](Phoenix.Component.html#live_render/3) for more information and examples. - `:as` - optionally configures the named helper. Defaults to `:live` when using a LiveView without actions or defaults to the LiveView name when using actions. - `:metadata` - a map to optional feed metadata used on telemetry events and route info, for example: `%{route_name: :foo, access: :user}`. This data can be retrieved by calling [`Phoenix.Router.route_info/4`](../phoenix/1.7.18/Phoenix.Router.html#route_info/4) with the `uri` from the `handle_params` callback. This can be used to customize a LiveView which may be invoked from different routes. - `:private` - an optional map of private data to put in the *plug connection*, for example: `%{route_name: :foo, access: :user}`. The data will be available inside `conn.private` in plug functions. ## [](Phoenix.LiveView.Router.html#live/4-examples)Examples ``` defmodule MyApp.Router use Phoenix.Router import Phoenix.LiveView.Router scope "/", MyApp do pipe_through [:browser] live "/thermostat", ThermostatLive live "/clock", ClockLive live "/dashboard", DashboardLive, container: {:main, class: "row"} end end iex> MyApp.Router.Helpers.live_path(MyApp.Endpoint, MyApp.ThermostatLive) "/thermostat" ``` [](Phoenix.LiveView.Router.html#live_session/3) # live\_session(name, opts \\\\ \[], list) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/router.ex#L229)(macro) Defines a live session for live redirects within a group of live routes. [`live_session/3`](Phoenix.LiveView.Router.html#live_session/3) allow routes defined with [`live/4`](Phoenix.LiveView.Router.html#live/4) to support `navigate` redirects from the client with navigation purely over the existing websocket connection. This allows live routes defined in the router to mount a new root LiveView without additional HTTP requests to the server. For backwards compatibility reasons, all live routes defined outside of any live session are considered part of a single unnamed live session. ## [](Phoenix.LiveView.Router.html#live_session/3-security-considerations)Security Considerations In a regular web application, we perform authentication and authorization checks on every request. Given LiveViews start as a regular HTTP request, they share the authentication logic with regular requests through plugs. Once the user is authenticated, we typically validate the sessions on the `mount` callback. Authorization rules generally happen on `mount` (for instance, is the user allowed to see this page?) and also on `handle_event` (is the user allowed to delete this item?). Performing authorization on mount is important because `navigate`s *do not go through the plug pipeline*. `live_session` can be used to draw boundaries between groups of LiveViews. Redirecting between `live_session`s will always force a full page reload and establish a brand new LiveView connection. This is useful when LiveViews require different authentication strategies or simply when they use different root layouts (as the root layout is not updated between live redirects). Please [read our guide on the security model](security-model.html) for a detailed description and general tips on authentication, authorization, and more. #### `live_session` and `forward` `live_session` does not currently work with `forward`. LiveView expects your `live` routes to always be directly defined within the main router of your application. #### `live_session` and `scope` Aliases set with [`Phoenix.Router.scope/2`](../phoenix/1.7.18/Phoenix.Router.html#scope/2) are not expanded in `live_session` arguments. You must use the full module name instead. ## [](Phoenix.LiveView.Router.html#live_session/3-options)Options - `:session` - An optional extra session map or MFA tuple to be merged with the LiveView session. For example, `%{"admin" => true}` or `{MyMod, :session, []}`. For MFA, the function is invoked and the [`Plug.Conn`](../plug/1.16.1/Plug.Conn.html) struct is prepended to the arguments list. - `:root_layout` - An optional root layout tuple for the initial HTTP render to override any existing root layout set in the router. - `:on_mount` - An optional list of hooks to attach to the mount lifecycle *of each LiveView in the session*. See [`Phoenix.LiveView.on_mount/1`](Phoenix.LiveView.html#on_mount/1). Passing a single value is also accepted. - `:layout` - An optional layout the LiveView will be rendered in. Setting this option overrides the layout via `use Phoenix.LiveView`. This option may be overridden inside a LiveView by returning `{:ok, socket, layout: ...}` from the mount callback ## [](Phoenix.LiveView.Router.html#live_session/3-examples)Examples ``` scope "/", MyAppWeb do pipe_through :browser live_session :default do live "/feed", FeedLive, :index live "/status", StatusLive, :index live "/status/:id", StatusLive, :show end live_session :admin, on_mount: MyAppWeb.AdminLiveAuth do live "/admin", AdminDashboardLive, :index live "/admin/posts", AdminPostLive, :index end end ``` In the example above, we have two live sessions. Live navigation between live views in the different sessions is not possible and will always require a full page reload. This is important in the example above because the `:admin` live session has authentication requirements, defined by `on_mount: MyAppWeb.AdminLiveAuth`, that the other LiveViews do not have. If you have both regular HTTP routes (via get, post, etc) and `live` routes, then you need to perform the same authentication and authorization rules in both. For example, if you were to add a `get "/admin/health"` entry point inside the `:admin` live session above, then you must create your own plug that performs the same authentication and authorization rules as `MyAppWeb.AdminLiveAuth`, and then pipe through it: ``` live_session :admin, on_mount: MyAppWeb.AdminLiveAuth do scope "/" do # Regular routes pipe_through [MyAppWeb.AdminPlugAuth] get "/admin/health", AdminHealthController, :index # Live routes live "/admin", AdminDashboardLive, :index live "/admin/posts", AdminPostLive, :index end end ``` The opposite is also true, if you have regular http routes and you want to add your own `live` routes, the same authentication and authorization checks executed by the plugs listed in `pipe_through` must be ported to LiveViews and be executed via `on_mount` hooks. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.Socket.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/socket.ex#L14 "View Source") Phoenix.LiveView.Socket (Phoenix LiveView v1.0.2) The LiveView socket for Phoenix Endpoints. This is typically mounted directly in your endpoint. ``` socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] ``` To share an underlying transport connection between regular Phoenix channels and LiveView processes, `use Phoenix.LiveView.Socket` from your own `MyAppWeb.UserSocket` module. Next, declare your `channel` definitions and optional `connect/3`, and `id/1` callbacks to handle your channel specific needs, then mount your own socket in your endpoint: ``` socket "/live", MyAppWeb.UserSocket, websocket: [connect_info: [session: @session_options]] ``` If you require session options to be set at runtime, you can use an MFA tuple. The function it designates must return a keyword list. ``` socket "/live", MyAppWeb.UserSocket, websocket: [connect_info: [session: {__MODULE__, :runtime_opts, []}]] # ... def runtime_opts() do Keyword.put(@session_options, :domain, host()) end ``` # [](Phoenix.LiveView.Socket.html#summary)Summary ## [Types](Phoenix.LiveView.Socket.html#types) [assigns()](Phoenix.LiveView.Socket.html#t:assigns/0) The data in a LiveView as stored in the socket. [assigns\_not\_in\_socket()](Phoenix.LiveView.Socket.html#t:assigns_not_in_socket/0) Struct returned when `assigns` is not in the socket. [fingerprints()](Phoenix.LiveView.Socket.html#t:fingerprints/0) [t()](Phoenix.LiveView.Socket.html#t:t/0) # [](Phoenix.LiveView.Socket.html#types)Types [](Phoenix.LiveView.Socket.html#t:assigns/0) # assigns() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/socket.ex#L79) ``` @type assigns() :: map() | assigns_not_in_socket() ``` The data in a LiveView as stored in the socket. [](Phoenix.LiveView.Socket.html#t:assigns_not_in_socket/0) # assigns\_not\_in\_socket() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/socket.ex#L76) ``` @opaque assigns_not_in_socket() ``` Struct returned when `assigns` is not in the socket. [](Phoenix.LiveView.Socket.html#t:fingerprints/0) # fingerprints() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/socket.ex#L81) ``` @type fingerprints() :: {nil, map()} | {binary(), map()} ``` [](Phoenix.LiveView.Socket.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/socket.ex#L83) ``` @type t() :: %Phoenix.LiveView.Socket{ assigns: assigns(), endpoint: module(), fingerprints: fingerprints(), host_uri: URI.t() | :not_mounted_at_router, id: binary(), parent_pid: nil | pid(), private: map(), redirected: nil | tuple(), root_pid: pid(), router: module(), transport_pid: pid() | nil, view: module() } ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.TagEngine.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L1 "View Source") Phoenix.LiveView.TagEngine behaviour (Phoenix LiveView v1.0.2) An EEx engine that understands tags. This cannot be directly used by Phoenix applications. Instead, it is the building block by engines such as [`Phoenix.LiveView.HTMLEngine`](Phoenix.LiveView.HTMLEngine.html). It is typically invoked like this: ``` EEx.compile_string(source, engine: Phoenix.LiveView.TagEngine, line: 1, file: path, caller: __CALLER__, source: source, tag_handler: FooBarEngine ) ``` Where `:tag_handler` implements the behaviour defined by this module. # [](Phoenix.LiveView.TagEngine.html#summary)Summary ## [Callbacks](Phoenix.LiveView.TagEngine.html#callbacks) [annotate\_body(caller)](Phoenix.LiveView.TagEngine.html#c:annotate_body/1) Callback invoked to add annotations around the whole body of a template. [annotate\_caller(file, line)](Phoenix.LiveView.TagEngine.html#c:annotate_caller/2) Callback invoked to add caller annotations before a function component is invoked. [classify\_type(name)](Phoenix.LiveView.TagEngine.html#c:classify_type/1) Classify the tag type from the given binary. [handle\_attributes(ast, meta)](Phoenix.LiveView.TagEngine.html#c:handle_attributes/2) Implements processing of attributes. [void?(name)](Phoenix.LiveView.TagEngine.html#c:void?/1) Returns if the given binary is either void or not. ## [Functions](Phoenix.LiveView.TagEngine.html#functions) [component(func, assigns, caller)](Phoenix.LiveView.TagEngine.html#component/3) Renders a component defined by the given function. [inner\_block(name, list)](Phoenix.LiveView.TagEngine.html#inner_block/2) Define a inner block, generally used by slots. # [](Phoenix.LiveView.TagEngine.html#callbacks)Callbacks [](Phoenix.LiveView.TagEngine.html#c:annotate_body/1) # annotate\_body(caller) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L59) ``` @callback annotate_body(caller :: Macro.Env.t()) :: {String.t(), String.t()} | nil ``` Callback invoked to add annotations around the whole body of a template. [](Phoenix.LiveView.TagEngine.html#c:annotate_caller/2) # annotate\_caller(file, line) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L64) ``` @callback annotate_caller(file :: String.t(), line :: integer()) :: String.t() | nil ``` Callback invoked to add caller annotations before a function component is invoked. [](Phoenix.LiveView.TagEngine.html#c:classify_type/1) # classify\_type(name) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L33) ``` @callback classify_type(name :: binary()) :: {type :: atom(), name :: binary()} ``` Classify the tag type from the given binary. This must return a tuple containing the type of the tag and the name of tag. For instance, for LiveView which uses HTML as default tag handler this would return `{:tag, 'div'}` in case the given binary is identified as HTML tag. You can also return {:error, "reason"} so that the compiler will display this error. [](Phoenix.LiveView.TagEngine.html#c:handle_attributes/2) # handle\_attributes(ast, meta) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L53) ``` @callback handle_attributes(ast :: Macro.t(), meta :: keyword()) :: {:attributes, [{binary(), Macro.t()} | Macro.t()]} | {:quoted, Macro.t()} ``` Implements processing of attributes. It returns a quoted expression or attributes. If attributes are returned, the second element is a list where each element in the list represents one attribute.If the list element is a two-element tuple, it is assumed the key is the name to be statically written in the template. The second element is the value which is also statically written to the template whenever possible (such as binaries or binaries inside a list). [](Phoenix.LiveView.TagEngine.html#c:void?/1) # void?(name) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L41) ``` @callback void?(name :: binary()) :: boolean() ``` Returns if the given binary is either void or not. That's mainly useful for HTML tags and used internally by the compiler. You can just implement as `def void?(_), do: false` if you want to ignore this. # [](Phoenix.LiveView.TagEngine.html#functions)Functions [](Phoenix.LiveView.TagEngine.html#component/3) # component(func, assigns, caller) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L88) Renders a component defined by the given function. This function is rarely invoked directly by users. Instead, it is used by `~H` and other engine implementations to render [`Phoenix.Component`](Phoenix.Component.html)s. For example, the following: ``` <MyApp.Weather.city name="Kraków" /> ``` Is the same as: ``` <%= component( &MyApp.Weather.city/1, [name: "Kraków"], {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line} ) %> ``` [](Phoenix.LiveView.TagEngine.html#inner_block/2) # inner\_block(name, list) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/tag_engine.ex#L129)(macro) Define a inner block, generally used by slots. This macro is mostly used by custom HTML engines that provide a `slot` implementation and rarely called directly. The `name` must be the assign name the slot/block will be stored under. If you're using HEEx templates, you should use its higher level `<:slot>` notation instead. See [`Phoenix.Component`](Phoenix.Component.html) for more information. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.UploadConfig.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_config.ex#L51 "View Source") Phoenix.LiveView.UploadConfig (Phoenix LiveView v1.0.2) The struct representing an upload. # [](Phoenix.LiveView.UploadConfig.html#summary)Summary ## [Types](Phoenix.LiveView.UploadConfig.html#types) [t()](Phoenix.LiveView.UploadConfig.html#t:t/0) # [](Phoenix.LiveView.UploadConfig.html#types)Types [](Phoenix.LiveView.UploadConfig.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_config.ex#L104) ``` @type t() :: %Phoenix.LiveView.UploadConfig{ accept: list() | :any, acceptable_exts: MapSet.t(), acceptable_types: MapSet.t(), allowed?: boolean(), auto_upload?: boolean(), chunk_size: term(), chunk_timeout: term(), cid: :unregistered | nil | integer(), client_key: String.t(), entries: list(), entry_refs_to_metas: %{required(String.t()) => map()}, entry_refs_to_pids: %{required(String.t()) => pid() | :unregistered | :done}, errors: list(), external: (Phoenix.LiveView.UploadEntry.t(), Phoenix.LiveView.Socket.t() -> {:ok | :error, meta :: %{uploader: String.t()}, Phoenix.LiveView.Socket.t()}) | false, max_entries: pos_integer(), max_file_size: pos_integer(), name: atom() | String.t(), progress_event: (name :: atom() | String.t(), Phoenix.LiveView.UploadEntry.t(), Phoenix.LiveView.Socket.t() -> {:noreply, Phoenix.LiveView.Socket.t()}) | nil, ref: String.t(), writer: (name :: atom() | String.t(), Phoenix.LiveView.UploadEntry.t(), Phoenix.LiveView.Socket.t() -> {module(), term()}) } ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.UploadEntry.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_config.ex#L1 "View Source") Phoenix.LiveView.UploadEntry (Phoenix LiveView v1.0.2) The struct representing an upload entry. # [](Phoenix.LiveView.UploadEntry.html#summary)Summary ## [Types](Phoenix.LiveView.UploadEntry.html#types) [t()](Phoenix.LiveView.UploadEntry.html#t:t/0) # [](Phoenix.LiveView.UploadEntry.html#types)Types [](Phoenix.LiveView.UploadEntry.html#t:t/0) # t() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_config.ex#L24) ``` @type t() :: %Phoenix.LiveView.UploadEntry{ cancelled?: boolean(), client_last_modified: integer() | nil, client_meta: map() | nil, client_name: String.t() | nil, client_relative_path: String.t() | nil, client_size: integer() | nil, client_type: String.t() | nil, done?: boolean(), preflighted?: term(), progress: integer(), ref: String.t() | nil, upload_config: String.t() | :atom, upload_ref: String.t(), uuid: String.t() | nil, valid?: boolean() } ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.UploadWriter.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_writer.ex#L1 "View Source") Phoenix.LiveView.UploadWriter behaviour (Phoenix LiveView v1.0.2) Provide a behavior for writing uploaded chunks to a final destination. By default, uploads are written to a temporary file on the server and consumed by the LiveView by reading the temporary file or copying it to durable location. Some usecases require custom handling of the uploaded chunks, such as streaming a user's upload to another server. In these cases, we don't want the chunks to be written to disk since we only need to forward them on. **Note**: Upload writers run inside the channel uploader process, so any blocking work will block the channel errors will crash the channel process. Custom implementations of [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) can be passed to `allow_upload/3`. To initialize the writer with options, define a 3-arity function that returns a tuple of `{writer, writer_opts}`. For example imagine an upload writer that logs the chunk sizes and tracks the total bytes sent by the client: ``` socket |> allow_upload(:avatar, accept: :any, writer: fn _name, _entry, _socket -> {EchoWriter, level: :debug} end ) ``` And such an `EchoWriter` could look like this: ``` defmodule EchoWriter do @behaviour Phoenix.LiveView.UploadWriter require Logger @impl true def init(opts) do {:ok, %{total: 0, level: Keyword.fetch!(opts, :level)}} end @impl true def meta(state), do: %{level: state.level} @impl true def write_chunk(data, state) do size = byte_size(data) Logger.log(state.level, "received chunk of #{size} bytes") {:ok, %{state | total: state.total + size}} end @impl true def close(state, reason) do Logger.log(state.level, "closing upload after #{state.total} bytes, #{inspect(reason)}") {:ok, state} end end ``` When the LiveView consumes the uploaded entry, it will receive the `%{level: ...}` returned from the meta callback. This allows the writer to keep state as it handles chunks to be later relayed to the LiveView when consumed. ## [](Phoenix.LiveView.UploadWriter.html#module-close-reasons)Close reasons The `close/2` callback is called when the upload is complete or cancelled. The following values can be passed: - `:done` - The client sent all expected chunks and the upload is awaiting consumption - `:cancel` - The upload was canceled, either by the server or the client navigating away. - `{:error, reason}` - The upload was canceled due to an error returned from `write_chunk/2`. For example, if `write_chunk/2` returns `{:error, :enoent, state}`, the upload will be cancelled and `close/2` will be called with the reason `{:error, :enoent}`. # [](Phoenix.LiveView.UploadWriter.html#summary)Summary ## [Callbacks](Phoenix.LiveView.UploadWriter.html#callbacks) [close(state, reason)](Phoenix.LiveView.UploadWriter.html#c:close/2) [init(opts)](Phoenix.LiveView.UploadWriter.html#c:init/1) [meta(state)](Phoenix.LiveView.UploadWriter.html#c:meta/1) [write\_chunk(data, state)](Phoenix.LiveView.UploadWriter.html#c:write_chunk/2) # [](Phoenix.LiveView.UploadWriter.html#callbacks)Callbacks [](Phoenix.LiveView.UploadWriter.html#c:close/2) # close(state, reason) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_writer.ex#L76) ``` @callback close(state :: term(), reason :: :done | :cancel | {:error, term()}) :: {:ok, state :: term()} | {:error, term()} ``` [](Phoenix.LiveView.UploadWriter.html#c:init/1) # init(opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_writer.ex#L72) ``` @callback init(opts :: term()) :: {:ok, state :: term()} | {:error, term()} ``` [](Phoenix.LiveView.UploadWriter.html#c:meta/1) # meta(state) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_writer.ex#L73) ``` @callback meta(state :: term()) :: map() ``` [](Phoenix.LiveView.UploadWriter.html#c:write_chunk/2) # write\_chunk(data, state) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/upload_writer.ex#L74) ``` @callback write_chunk(data :: binary(), state :: term()) :: {:ok, state :: term()} | {:error, reason :: term(), state :: term()} ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveView.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1 "View Source") Phoenix.LiveView behaviour (Phoenix LiveView v1.0.2) A LiveView is a process that receives events, updates its state, and renders updates to a page as diffs. To get started, see [the Welcome guide](welcome.html). This module provides advanced documentation and features about using LiveView. ## [](Phoenix.LiveView.html#module-life-cycle)Life-cycle A LiveView begins as a regular HTTP request and HTML response, and then upgrades to a stateful view on client connect, guaranteeing a regular HTML page even if JavaScript is disabled. Any time a stateful view changes or updates its socket assigns, it is automatically re-rendered and the updates are pushed to the client. Socket assigns are stateful values kept on the server side in [`Phoenix.LiveView.Socket`](Phoenix.LiveView.Socket.html). This is different from the common stateless HTTP pattern of sending the connection state to the client in the form of a token or cookie and rebuilding the state on the server to service every request. You begin by rendering a LiveView typically from your router. When LiveView is first rendered, the [`mount/3`](Phoenix.LiveView.html#c:mount/3) callback is invoked with the current params, the current session and the LiveView socket. As in a regular request, `params` contains public data that can be modified by the user. The `session` always contains private data set by the application itself. The [`mount/3`](Phoenix.LiveView.html#c:mount/3) callback wires up socket assigns necessary for rendering the view. After mounting, [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) is invoked so uri and query params are handled. Finally, [`render/1`](Phoenix.LiveView.html#c:render/1) is invoked and the HTML is sent as a regular HTML response to the client. After rendering the static page, LiveView connects from the client to the server where stateful views are spawned to push rendered updates to the browser, and receive client events via `phx-` bindings. Just like the first rendering, [`mount/3`](Phoenix.LiveView.html#c:mount/3), is invoked with params, session, and socket state. However in the connected client case, a LiveView process is spawned on the server, runs [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) again and then pushes the result of [`render/1`](Phoenix.LiveView.html#c:render/1) to the client and continues on for the duration of the connection. If at any point during the stateful life-cycle a crash is encountered, or the client connection drops, the client gracefully reconnects to the server, calling [`mount/3`](Phoenix.LiveView.html#c:mount/3) and [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) again. LiveView also allows attaching hooks to specific life-cycle stages with [`attach_hook/4`](Phoenix.LiveView.html#attach_hook/4). ## [](Phoenix.LiveView.html#module-template-collocation)Template collocation There are two possible ways of rendering content in a LiveView. The first one is by explicitly defining a render function, which receives `assigns` and returns a `HEEx` template defined with [the `~H` sigil](Phoenix.Component.html#sigil_H/2). ``` defmodule MyAppWeb.DemoLive do # In a typical Phoenix app, the following line would usually be `use MyAppWeb, :live_view` use Phoenix.LiveView def render(assigns) do ~H""" Hello world! """ end end ``` For larger templates, you can place them in a file in the same directory and same name as the LiveView. For example, if the file above is placed at `lib/my_app_web/live/demo_live.ex`, you can also remove the `render/1` function altogether and put the template code at `lib/my_app_web/live/demo_live.html.heex`. ## [](Phoenix.LiveView.html#module-async-operations)Async Operations Performing asynchronous work is common in LiveViews and LiveComponents. It allows the user to get a working UI quickly while the system fetches some data in the background or talks to an external service, without blocking the render or event handling. For async work, you also typically need to handle the different states of the async operation, such as loading, error, and the successful result. You also want to catch any errors or exits and translate it to a meaningful update in the UI rather than crashing the user experience. ### [](Phoenix.LiveView.html#module-async-assigns)Async assigns The [`assign_async/3`](Phoenix.LiveView.html#assign_async/3) function takes the socket, a key or list of keys which will be assigned asynchronously, and a function. This function will be wrapped in a `task` by `assign_async`, making it easy for you to return the result. This function must return an `{:ok, assigns}` or `{:error, reason}` tuple, where `assigns` is a map of the keys passed to `assign_async`. If the function returns anything else, an error is raised. The task is only started when the socket is connected. For example, let's say we want to async fetch a user's organization from the database, as well as their profile and rank: ``` def mount(%{"slug" => slug}, _, socket) do {:ok, socket |> assign(:foo, "bar") |> assign_async(:org, fn -> {:ok, %{org: fetch_org!(slug)}} end) |> assign_async([:profile, :rank], fn -> {:ok, %{profile: ..., rank: ...}} end)} end ``` ### [](Phoenix.LiveView.html#module-warning)Warning When using async operations it is important to not pass the socket into the function as it will copy the whole socket struct to the Task process, which can be very expensive. Instead of: ``` assign_async(:org, fn -> {:ok, %{org: fetch_org(socket.assigns.slug)}} end) ``` We should do: ``` slug = socket.assigns.slug assign_async(:org, fn -> {:ok, %{org: fetch_org(slug)}} end) ``` See: [https://hexdocs.pm/elixir/process-anti-patterns.html#sending-unnecessary-data](../elixir/process-anti-patterns.html#sending-unnecessary-data) The state of the async operation is stored in the socket assigns within an [`Phoenix.LiveView.AsyncResult`](Phoenix.LiveView.AsyncResult.html). It carries the loading and failed states, as well as the result. For example, if we wanted to show the loading states in the UI for the `:org`, our template could conditionally render the states: ``` <div :if={@org.loading}>Loading organization...</div> <div :if={org = @org.ok? && @org.result}>{org.name} loaded!</div> ``` The [`Phoenix.Component.async_result/1`](Phoenix.Component.html#async_result/1) function component can also be used to declaratively render the different states using slots: ``` <.async_result :let={org} assign={@org}> <:loading>Loading organization...</:loading> <:failed :let={_failure}>there was an error loading the organization</:failed> {org.name} </.async_result> ``` ### [](Phoenix.LiveView.html#module-arbitrary-async-operations)Arbitrary async operations Sometimes you need lower level control of asynchronous operations, while still receiving process isolation and error handling. For this, you can use [`start_async/3`](Phoenix.LiveView.html#start_async/3) and the [`Phoenix.LiveView.AsyncResult`](Phoenix.LiveView.AsyncResult.html) module directly: ``` def mount(%{"id" => id}, _, socket) do {:ok, socket |> assign(:org, AsyncResult.loading()) |> start_async(:my_task, fn -> fetch_org!(id) end)} end def handle_async(:my_task, {:ok, fetched_org}, socket) do %{org: org} = socket.assigns {:noreply, assign(socket, :org, AsyncResult.ok(org, fetched_org))} end def handle_async(:my_task, {:exit, reason}, socket) do %{org: org} = socket.assigns {:noreply, assign(socket, :org, AsyncResult.failed(org, {:exit, reason}))} end ``` [`start_async/3`](Phoenix.LiveView.html#start_async/3) is used to fetch the organization asynchronously. The [`handle_async/3`](Phoenix.LiveView.html#c:handle_async/3) callback is called when the task completes or exits, with the results wrapped in either `{:ok, result}` or `{:exit, reason}`. The `AsyncResult` module provides functions to update the state of the async operation, but you can also assign any value directly to the socket if you want to handle the state yourself. ## [](Phoenix.LiveView.html#module-endpoint-configuration)Endpoint configuration LiveView accepts the following configuration in your endpoint under the `:live_view` key: - `:signing_salt` (required) - the salt used to sign data sent to the client - `:hibernate_after` (optional) - the idle time in milliseconds allowed in the LiveView before compressing its own memory and state. Defaults to 15000ms (15 seconds) # [](Phoenix.LiveView.html#summary)Summary ## [Types](Phoenix.LiveView.html#types) [unsigned\_params()](Phoenix.LiveView.html#t:unsigned_params/0) ## [Callbacks](Phoenix.LiveView.html#callbacks) [handle\_async(name, async\_fun\_result, socket)](Phoenix.LiveView.html#c:handle_async/3) Invoked when the result of an [`start_async/3`](Phoenix.LiveView.html#start_async/3) operation is available. [handle\_call(msg, {}, socket)](Phoenix.LiveView.html#c:handle_call/3) Invoked to handle calls from other Elixir processes. [handle\_cast(msg, socket)](Phoenix.LiveView.html#c:handle_cast/2) Invoked to handle casts from other Elixir processes. [handle\_event(event, unsigned\_params, socket)](Phoenix.LiveView.html#c:handle_event/3) Invoked to handle events sent by the client. [handle\_info(msg, socket)](Phoenix.LiveView.html#c:handle_info/2) Invoked to handle messages from other Elixir processes. [handle\_params(unsigned\_params, uri, socket)](Phoenix.LiveView.html#c:handle_params/3) Invoked after mount and whenever there is a live patch event. [mount(params, session, socket)](Phoenix.LiveView.html#c:mount/3) The LiveView entry-point. [render(assigns)](Phoenix.LiveView.html#c:render/1) Renders a template. [terminate(reason, socket)](Phoenix.LiveView.html#c:terminate/2) Invoked when the LiveView is terminating. ## [Functions](Phoenix.LiveView.html#functions) [\_\_live\_\_(opts \\\\ \[\])](Phoenix.LiveView.html#__live__/1) Defines metadata for a LiveView. [\_\_using\_\_(opts)](Phoenix.LiveView.html#__using__/1) Uses LiveView in the current module to mark it a LiveView. [allow\_upload(socket, name, options)](Phoenix.LiveView.html#allow_upload/3) Allows an upload for the provided name. [assign\_async(socket, key\_or\_keys, func, opts \\\\ \[\])](Phoenix.LiveView.html#assign_async/4) Assigns keys asynchronously. [attach\_hook(socket, name, stage, fun)](Phoenix.LiveView.html#attach_hook/4) Attaches the given `fun` by `name` for the lifecycle `stage` into `socket`. [cancel\_async(socket, async\_or\_keys, reason \\\\ {:shutdown, :cancel})](Phoenix.LiveView.html#cancel_async/3) Cancels an async operation if one exists. [cancel\_upload(socket, name, entry\_ref)](Phoenix.LiveView.html#cancel_upload/3) Cancels an upload for the given entry. [clear\_flash(socket)](Phoenix.LiveView.html#clear_flash/1) Clears the flash. [clear\_flash(socket, key)](Phoenix.LiveView.html#clear_flash/2) Clears a key from the flash. [connected?(socket)](Phoenix.LiveView.html#connected?/1) Returns true if the socket is connected. [consume\_uploaded\_entries(socket, name, func)](Phoenix.LiveView.html#consume_uploaded_entries/3) Consumes the uploaded entries. [consume\_uploaded\_entry(socket, entry, func)](Phoenix.LiveView.html#consume_uploaded_entry/3) Consumes an individual uploaded entry. [detach\_hook(socket, name, stage)](Phoenix.LiveView.html#detach_hook/3) Detaches a hook with the given `name` from the lifecycle `stage`. [disallow\_upload(socket, name)](Phoenix.LiveView.html#disallow_upload/2) Revokes a previously allowed upload from [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3). [get\_connect\_info(socket, key)](Phoenix.LiveView.html#get_connect_info/2) Accesses a given connect info key from the socket. [get\_connect\_params(socket)](Phoenix.LiveView.html#get_connect_params/1) Accesses the connect params sent by the client for use on connected mount. [on\_mount(mod\_or\_mod\_arg)](Phoenix.LiveView.html#on_mount/1) Declares a module callback to be invoked on the LiveView's mount. [push\_event(socket, event, payload)](Phoenix.LiveView.html#push_event/3) Pushes an event to the client. [push\_navigate(socket, opts)](Phoenix.LiveView.html#push_navigate/2) Annotates the socket for navigation to another LiveView in the same `live_session`. [push\_patch(socket, opts)](Phoenix.LiveView.html#push_patch/2) Annotates the socket for navigation within the current LiveView. [put\_flash(socket, kind, msg)](Phoenix.LiveView.html#put_flash/3) Adds a flash message to the socket to be displayed. [put\_private(socket, key, value)](Phoenix.LiveView.html#put_private/3) Puts a new private key and value in the socket. [redirect(socket, opts \\\\ \[\])](Phoenix.LiveView.html#redirect/2) Annotates the socket for redirect to a destination path. [render\_with(socket, component)](Phoenix.LiveView.html#render_with/2) Configures which function to use to render a LiveView/LiveComponent. [send\_update(pid \\\\ self(), module\_or\_cid, assigns)](Phoenix.LiveView.html#send_update/3) Asynchronously updates a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) with new assigns. [send\_update\_after(pid \\\\ self(), module\_or\_cid, assigns, time\_in\_milliseconds)](Phoenix.LiveView.html#send_update_after/4) Similar to [`send_update/3`](Phoenix.LiveView.html#send_update/3) but the update will be delayed according to the given `time_in_milliseconds`. [start\_async(socket, name, func, opts \\\\ \[\])](Phoenix.LiveView.html#start_async/4) Wraps your function in an asynchronous task and invokes a callback `name` to handle the result. [static\_changed?(socket)](Phoenix.LiveView.html#static_changed?/1) Returns true if the socket is connected and the tracked static assets have changed. [stream(socket, name, items, opts \\\\ \[\])](Phoenix.LiveView.html#stream/4) Assigns a new stream to the socket or inserts items into an existing stream. Returns an updated `socket`. [stream\_configure(socket, name, opts)](Phoenix.LiveView.html#stream_configure/3) Configures a stream. [stream\_delete(socket, name, item)](Phoenix.LiveView.html#stream_delete/3) Deletes an item from the stream. [stream\_delete\_by\_dom\_id(socket, name, id)](Phoenix.LiveView.html#stream_delete_by_dom_id/3) Deletes an item from the stream given its computed DOM id. [stream\_insert(socket, name, item, opts \\\\ \[\])](Phoenix.LiveView.html#stream_insert/4) Inserts a new item or updates an existing item in the stream. [transport\_pid(socket)](Phoenix.LiveView.html#transport_pid/1) Returns the transport pid of the socket. [uploaded\_entries(socket, name)](Phoenix.LiveView.html#uploaded_entries/2) Returns the completed and in progress entries for the upload. # [](Phoenix.LiveView.html#types)Types [](Phoenix.LiveView.html#t:unsigned_params/0) # unsigned\_params() [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L191) ``` @type unsigned_params() :: map() ``` # [](Phoenix.LiveView.html#callbacks)Callbacks [](Phoenix.LiveView.html#c:handle_async/3) # handle\_async(name, async\_fun\_result, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L324)(optional) ``` @callback handle_async( name :: term(), async_fun_result :: {:ok, term()} | {:exit, term()}, socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()} ``` Invoked when the result of an [`start_async/3`](Phoenix.LiveView.html#start_async/3) operation is available. For a deeper understanding of using this callback, refer to the ["Arbitrary async operations"](Phoenix.LiveView.html#module-arbitrary-async-operations) section. [](Phoenix.LiveView.html#c:handle_call/3) # handle\_call(msg, {}, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L293)(optional) ``` @callback handle_call( msg :: term(), {pid(), reference()}, socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()} | {:reply, term(), Phoenix.LiveView.Socket.t()} ``` Invoked to handle calls from other Elixir processes. See [`GenServer.call/3`](../elixir/GenServer.html#call/3) and [`GenServer.handle_call/3`](../elixir/GenServer.html#c:handle_call/3) for more information. [](Phoenix.LiveView.html#c:handle_cast/2) # handle\_cast(msg, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L304)(optional) ``` @callback handle_cast(msg :: term(), socket :: Phoenix.LiveView.Socket.t()) :: {:noreply, Phoenix.LiveView.Socket.t()} ``` Invoked to handle casts from other Elixir processes. See [`GenServer.cast/2`](../elixir/GenServer.html#cast/2) and [`GenServer.handle_cast/2`](../elixir/GenServer.html#c:handle_cast/2) for more information. It must always return `{:noreply, socket}`, where `:noreply` means no additional information is sent to the process which cast the message. [](Phoenix.LiveView.html#c:handle_event/3) # handle\_event(event, unsigned\_params, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L284)(optional) ``` @callback handle_event( event :: binary(), unsigned_params(), socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()} | {:reply, map(), Phoenix.LiveView.Socket.t()} ``` Invoked to handle events sent by the client. It receives the `event` name, the event payload as a map, and the socket. It must return `{:noreply, socket}`, where `:noreply` means no additional information is sent to the client, or `{:reply, map(), socket}`, where the given `map()` is encoded and sent as a reply to the client. [](Phoenix.LiveView.html#c:handle_info/2) # handle\_info(msg, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L315)(optional) ``` @callback handle_info(msg :: term(), socket :: Phoenix.LiveView.Socket.t()) :: {:noreply, Phoenix.LiveView.Socket.t()} ``` Invoked to handle messages from other Elixir processes. See [`Kernel.send/2`](../elixir/Kernel.html#send/2) and [`GenServer.handle_info/2`](../elixir/GenServer.html#c:handle_info/2) for more information. It must always return `{:noreply, socket}`, where `:noreply` means no additional information is sent to the process which sent the message. [](Phoenix.LiveView.html#c:handle_params/3) # handle\_params(unsigned\_params, uri, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L270)(optional) ``` @callback handle_params( unsigned_params(), uri :: String.t(), socket :: Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()} ``` Invoked after mount and whenever there is a live patch event. It receives the current `params`, including parameters from the router, the current `uri` from the client and the `socket`. It is invoked after mount or whenever there is a live navigation event caused by [`push_patch/2`](Phoenix.LiveView.html#push_patch/2) or `<.link patch={...}>`. It must always return `{:noreply, socket}`, where `:noreply` means no additional information is sent to the client. #### Note `handle_params` is only allowed on LiveViews mounted at the router, as it takes the current url of the page as the second parameter. [](Phoenix.LiveView.html#c:mount/3) # mount(params, session, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L221)(optional) ``` @callback mount( params :: unsigned_params() | :not_mounted_at_router, session :: map(), socket :: Phoenix.LiveView.Socket.t() ) :: {:ok, Phoenix.LiveView.Socket.t()} | {:ok, Phoenix.LiveView.Socket.t(), keyword()} ``` The LiveView entry-point. For each LiveView in the root of a template, [`mount/3`](Phoenix.LiveView.html#c:mount/3) is invoked twice: once to do the initial page load and again to establish the live socket. It expects three arguments: - `params` - a map of string keys which contain public information that can be set by the user. The map contains the query params as well as any router path parameter. If the LiveView was not mounted at the router, this argument is the atom `:not_mounted_at_router` - `session` - the connection session - `socket` - the LiveView socket It must return either `{:ok, socket}` or `{:ok, socket, options}`, where `options` is one of: - `:temporary_assigns` - a keyword list of assigns that are temporary and must be reset to their value after every render. Note that once the value is reset, it won't be re-rendered again until it is explicitly assigned - `:layout` - the optional layout to be used by the LiveView. Setting this option will override any layout previously set via [`Phoenix.LiveView.Router.live_session/2`](Phoenix.LiveView.Router.html#live_session/2) or on `use Phoenix.LiveView` [](Phoenix.LiveView.html#c:render/1) # render(assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L243)(optional) ``` @callback render(assigns :: Phoenix.LiveView.Socket.assigns()) :: Phoenix.LiveView.Rendered.t() ``` Renders a template. This callback is invoked whenever LiveView detects new content must be rendered and sent to the client. If you define this function, it must return a template defined via the [`Phoenix.Component.sigil_H/2`](Phoenix.Component.html#sigil_H/2). If you don't define this function, LiveView will attempt to render a template in the same directory as your LiveView. For example, if you have a LiveView named `MyApp.MyCustomView` inside `lib/my_app/live_views/my_custom_view.ex`, Phoenix will look for a template at `lib/my_app/live_views/my_custom_view.html.heex`. [](Phoenix.LiveView.html#c:terminate/2) # terminate(reason, socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L251)(optional) ``` @callback terminate(reason, socket :: Phoenix.LiveView.Socket.t()) :: term() when reason: :normal | :shutdown | {:shutdown, :left | :closed | term()} ``` Invoked when the LiveView is terminating. In case of errors, this callback is only invoked if the LiveView is trapping exits. See [`GenServer.terminate/2`](../elixir/GenServer.html#c:terminate/2) for more info. # [](Phoenix.LiveView.html#functions)Functions [](Phoenix.LiveView.html#__live__/1) # \_\_live\_\_(opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L431) Defines metadata for a LiveView. This must be returned from the `__live__` callback. It accepts: - `:container` - an optional tuple for the HTML tag and DOM attributes to be used for the LiveView container. For example: `{:li, style: "color: blue;"}`. - `:layout` - configures the layout the LiveView will be rendered in. This layout can be overridden by on [`mount/3`](Phoenix.LiveView.html#c:mount/3) or via the `:layout` option in [`Phoenix.LiveView.Router.live_session/2`](Phoenix.LiveView.Router.html#live_session/2) - `:log` - configures the log level for the LiveView, either `false` or a log level - `:on_mount` - a list of tuples with module names and argument to be invoked as `on_mount` hooks [](Phoenix.LiveView.html#__using__/1) # \_\_using\_\_(opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L367)(macro) Uses LiveView in the current module to mark it a LiveView. ``` use Phoenix.LiveView, container: {:tr, class: "colorized"}, layout: {MyAppWeb.Layouts, :app}, log: :info ``` ## [](Phoenix.LiveView.html#__using__/1-options)Options - `:container` - an optional tuple for the HTML tag and DOM attributes to be used for the LiveView container. For example: `{:li, style: "color: blue;"}`. See [`Phoenix.Component.live_render/3`](Phoenix.Component.html#live_render/3) for more information and examples. - `:global_prefixes` - the global prefixes to use for components. See `Global Attributes` in [`Phoenix.Component`](Phoenix.Component.html) for more information. - `:layout` - configures the layout the LiveView will be rendered in. This layout can be overridden by on [`mount/3`](Phoenix.LiveView.html#c:mount/3) or via the `:layout` option in [`Phoenix.LiveView.Router.live_session/2`](Phoenix.LiveView.Router.html#live_session/2) - `:log` - configures the log level for the LiveView, either `false` or a log level [](Phoenix.LiveView.html#allow_upload/3) # allow\_upload(socket, name, options) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L839) Allows an upload for the provided name. ## [](Phoenix.LiveView.html#allow_upload/3-options)Options - `:accept` - Required. A list of unique file extensions (such as ".jpeg") or mime type (such as "image/jpeg" or "image/\*"). You may also pass the atom `:any` instead of a list to support to allow any kind of file. For example, `[".jpeg"]`, `:any`, etc. - `:max_entries` - The maximum number of selected files to allow per file input. Defaults to 1. - `:max_file_size` - The maximum file size in bytes to allow to be uploaded. Defaults 8MB. For example, `12_000_000`. - `:chunk_size` - The chunk size in bytes to send when uploading. Defaults `64_000`. - `:chunk_timeout` - The time in milliseconds to wait before closing the upload channel when a new chunk has not been received. Defaults to `10_000`. - `:external` - A 2-arity function for generating metadata for external client uploaders. This function must return either `{:ok, meta, socket}` or `{:error, meta, socket}` where meta is a map. See the Uploads section for example usage. - `:progress` - An optional 3-arity function for receiving progress events. - `:auto_upload` - Instructs the client to upload the file automatically on file selection instead of waiting for form submits. Defaults to `false`. - `:writer` - A module implementing the [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) behaviour to use for writing the uploaded chunks. Defaults to writing to a temporary file for consumption. See the [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) docs for custom usage. Raises when a previously allowed upload under the same name is still active. ## [](Phoenix.LiveView.html#allow_upload/3-examples)Examples ``` allow_upload(socket, :avatar, accept: ~w(.jpg .jpeg), max_entries: 2) allow_upload(socket, :avatar, accept: :any) ``` For consuming files automatically as they are uploaded, you can pair `auto_upload: true` with a custom progress function to consume the entries as they are completed. For example: ``` allow_upload(socket, :avatar, accept: :any, progress: &handle_progress/3, auto_upload: true) defp handle_progress(:avatar, entry, socket) do if entry.done? do uploaded_file = consume_uploaded_entry(socket, entry, fn %{} = meta -> {:ok, ...} end) {:noreply, put_flash(socket, :info, "file #{uploaded_file.name} uploaded")} else {:noreply, socket} end end ``` [](Phoenix.LiveView.html#assign_async/4) # assign\_async(socket, key\_or\_keys, func, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L2066)(macro) Assigns keys asynchronously. Wraps your function in a task linked to the caller, errors are wrapped. Each key passed to [`assign_async/3`](Phoenix.LiveView.html#assign_async/3) will be assigned to an `%AsyncResult{}` struct holding the status of the operation and the result when the function completes. The task is only started when the socket is connected. ## [](Phoenix.LiveView.html#assign_async/4-options)Options - `:supervisor` - allows you to specify a [`Task.Supervisor`](../elixir/Task.Supervisor.html) to supervise the task. - `:reset` - remove previous results during async operation when true. Possible values are `true`, `false`, or a list of keys to reset. Defaults to `false`. ## [](Phoenix.LiveView.html#assign_async/4-examples)Examples ``` def mount(%{"slug" => slug}, _, socket) do {:ok, socket |> assign(:foo, "bar") |> assign_async(:org, fn -> {:ok, %{org: fetch_org!(slug)}} end) |> assign_async([:profile, :rank], fn -> {:ok, %{profile: ..., rank: ...}} end)} end ``` See the moduledoc for more information. ## [](Phoenix.LiveView.html#assign_async/4-assign_async-3-and-send_update-3)[`assign_async/3`](Phoenix.LiveView.html#assign_async/3) and [`send_update/3`](Phoenix.LiveView.html#send_update/3) Since the code inside [`assign_async/3`](Phoenix.LiveView.html#assign_async/3) runs in a separate process, `send_update(Component, data)` does not work inside [`assign_async/3`](Phoenix.LiveView.html#assign_async/3), since [`send_update/2`](Phoenix.LiveView.html#send_update/2) assumes it is running inside the LiveView process. The solution is to explicitly send the update to the LiveView: ``` parent = self() assign_async(socket, :org, fn -> # ... send_update(parent, Component, data) end) ``` ## [](Phoenix.LiveView.html#assign_async/4-testing-async-operations)Testing async operations When testing LiveViews and LiveComponents with async assigns, use [`Phoenix.LiveViewTest.render_async/2`](Phoenix.LiveViewTest.html#render_async/2) to ensure the test waits until the async operations are complete before proceeding with assertions or before ending the test. For example: ``` {:ok, view, _html} = live(conn, "/my_live_view") html = render_async(view) assert html =~ "My assertion" ``` Not calling `render_async/2` to ensure all async assigns have finished might result in errors in cases where your process has side effects: ``` [error] MyXQL.Connection (#PID<0.308.0>) disconnected: ** (DBConnection.ConnectionError) client #PID<0.794.0> ``` [](Phoenix.LiveView.html#attach_hook/4) # attach\_hook(socket, name, stage, fun) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1565) Attaches the given `fun` by `name` for the lifecycle `stage` into `socket`. > Note: This function is for server-side lifecycle callbacks. For client-side hooks, see the [JS Interop guide](js-interop.html#client-hooks-via-phx-hook). Hooks provide a mechanism to tap into key stages of the LiveView lifecycle in order to bind/update assigns, intercept events, patches, and regular messages when necessary, and to inject common functionality. Use [`attach_hook/4`](Phoenix.LiveView.html#attach_hook/4) on any of the following lifecycle stages: `:handle_params`, `:handle_event`, `:handle_info`, `:handle_async`, and `:after_render`. To attach a hook to the `:mount` stage, use [`on_mount/1`](Phoenix.LiveView.html#on_mount/1). > Note: only `:after_render` and `:handle_event` hooks are currently supported in LiveComponents. ## [](Phoenix.LiveView.html#attach_hook/4-return-values)Return Values Lifecycle hooks take place immediately before a given lifecycle callback is invoked on the LiveView. With the exception of `:after_render`, a hook may return `{:halt, socket}` to halt the reduction, otherwise it must return `{:cont, socket}` so the operation may continue until all hooks have been invoked for the current stage. For `:after_render` hooks, the `socket` itself must be returned. Any updates to the socket assigns *will not* trigger a new render or diff calculation to the client. ## [](Phoenix.LiveView.html#attach_hook/4-halting-the-lifecycle)Halting the lifecycle Note that halting from a hook *will halt the entire lifecycle stage*. This means that when a hook returns `{:halt, socket}` then the LiveView callback will **not** be invoked. This has some implications. ### [](Phoenix.LiveView.html#attach_hook/4-implications-for-plugin-authors)Implications for plugin authors When defining a plugin that matches on specific callbacks, you **must** define a catch-all clause, as your hook will be invoked even for events you may not be interested in. ### [](Phoenix.LiveView.html#attach_hook/4-implications-for-end-users)Implications for end-users Allowing a hook to halt the invocation of the callback means that you can attach hooks to intercept specific events before detaching themselves, while allowing other events to continue normally. ## [](Phoenix.LiveView.html#attach_hook/4-replying-to-events)Replying to events Hooks attached to the `:handle_event` stage are able to reply to client events by returning `{:halt, reply, socket}`. This is useful especially for [JavaScript interoperability](js-interop.html#client-hooks-via-phx-hook) because a client hook can push an event and receive a reply. ## [](Phoenix.LiveView.html#attach_hook/4-examples)Examples Attaching and detaching a hook: ``` def mount(_params, _session, socket) do socket = attach_hook(socket, :my_hook, :handle_event, fn "very-special-event", _params, socket -> # Handle the very special event and then detach the hook {:halt, detach_hook(socket, :my_hook, :handle_event)} _event, _params, socket -> {:cont, socket} end) {:ok, socket} end ``` Replying to a client event: ``` /** * @type {Object.<string, import("phoenix_live_view").ViewHook>} */ let Hooks = {} Hooks.ClientHook = { mounted() { this.pushEvent("ClientHook:mounted", {hello: "world"}, (reply) => { console.log("received reply:", reply) }) } } let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, ...}) ``` ``` def render(assigns) do ~H""" <div id="my-client-hook" phx-hook="ClientHook"></div> """ end def mount(_params, _session, socket) do socket = attach_hook(socket, :reply_on_client_hook_mounted, :handle_event, fn "ClientHook:mounted", params, socket -> {:halt, params, socket} _, _, socket -> {:cont, socket} end) {:ok, socket} end ``` [](Phoenix.LiveView.html#cancel_async/3) # cancel\_async(socket, async\_or\_keys, reason \\\\ {:shutdown, :cancel}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L2133) Cancels an async operation if one exists. Accepts either the `%AsyncResult{}` when using [`assign_async/3`](Phoenix.LiveView.html#assign_async/3) or the key passed to [`start_async/3`](Phoenix.LiveView.html#start_async/3). The underlying process will be killed with the provided reason, or with `{:shutdown, :cancel}` if no reason is passed. For [`assign_async/3`](Phoenix.LiveView.html#assign_async/3) operations, the `:failed` field will be set to `{:exit, reason}`. For [`start_async/3`](Phoenix.LiveView.html#start_async/3), the [`handle_async/3`](Phoenix.LiveView.html#c:handle_async/3) callback will receive `{:exit, reason}` as the result. Returns the `%Phoenix.LiveView.Socket{}`. ## [](Phoenix.LiveView.html#cancel_async/3-examples)Examples ``` cancel_async(socket, :preview) cancel_async(socket, :preview, :my_reason) cancel_async(socket, socket.assigns.preview) ``` [](Phoenix.LiveView.html#cancel_upload/3) # cancel\_upload(socket, name, entry\_ref) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L866) Cancels an upload for the given entry. ## [](Phoenix.LiveView.html#cancel_upload/3-examples)Examples ``` <%= for entry <- @uploads.avatar.entries do %> ... <button phx-click="cancel-upload" phx-value-ref={entry.ref}>cancel</button> <% end %> ``` ``` def handle_event("cancel-upload", %{"ref" => ref}, socket) do {:noreply, cancel_upload(socket, :avatar, ref)} end ``` [](Phoenix.LiveView.html#clear_flash/1) # clear\_flash(socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L707) Clears the flash. ## [](Phoenix.LiveView.html#clear_flash/1-examples)Examples ``` iex> clear_flash(socket) ``` Clearing the flash can also be triggered on the client and natively handled by LiveView using the `lv:clear-flash` event. For example: ``` <p class="alert" phx-click="lv:clear-flash"> {Phoenix.Flash.get(@flash, :info)} </p> ``` [](Phoenix.LiveView.html#clear_flash/2) # clear\_flash(socket, key) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L726) Clears a key from the flash. ## [](Phoenix.LiveView.html#clear_flash/2-examples)Examples ``` iex> clear_flash(socket, :info) ``` Clearing the flash can also be triggered on the client and natively handled by LiveView using the `lv:clear-flash` event. For example: ``` <p class="alert" phx-click="lv:clear-flash" phx-value-key="info"> {Phoenix.Flash.get(@flash, :info)} </p> ``` [](Phoenix.LiveView.html#connected?/1) # connected?(socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L595) Returns true if the socket is connected. Useful for checking the connectivity status when mounting the view. For example, on initial page render, the view is mounted statically, rendered, and the HTML is sent to the client. Once the client connects to the server, a LiveView is then spawned and mounted statefully within a process. Use [`connected?/1`](Phoenix.LiveView.html#connected?/1) to conditionally perform stateful work, such as subscribing to pubsub topics, sending messages, etc. ## [](Phoenix.LiveView.html#connected?/1-examples)Examples ``` defmodule DemoWeb.ClockLive do use Phoenix.LiveView ... def mount(_params, _session, socket) do if connected?(socket), do: :timer.send_interval(1000, self(), :tick) {:ok, assign(socket, date: :calendar.local_time())} end def handle_info(:tick, socket) do {:noreply, assign(socket, date: :calendar.local_time())} end end ``` [](Phoenix.LiveView.html#consume_uploaded_entries/3) # consume\_uploaded\_entries(socket, name, func) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L912) Consumes the uploaded entries. Raises when there are still entries in progress. Typically called when submitting a form to handle the uploaded entries alongside the form data. For form submissions, it is guaranteed that all entries have completed before the submit event is invoked. Once entries are consumed, they are removed from the upload. The function passed to consume may return a tagged tuple of the form `{:ok, my_result}` to collect results about the consumed entries, or `{:postpone, my_result}` to collect results, but postpone the file consumption to be performed later. A list of all `my_result` values produced by the passed function is returned, regardless of whether they were consumed or postponed. ## [](Phoenix.LiveView.html#consume_uploaded_entries/3-examples)Examples ``` def handle_event("save", _params, socket) do uploaded_files = consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry -> dest = Path.join("priv/static/uploads", Path.basename(path)) File.cp!(path, dest) {:ok, ~p"/uploads/#{Path.basename(dest)}"} end) {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))} end ``` [](Phoenix.LiveView.html#consume_uploaded_entry/3) # consume\_uploaded\_entry(socket, entry, func) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L949) Consumes an individual uploaded entry. Raises when the entry is still in progress. Typically called when submitting a form to handle the uploaded entries alongside the form data. Once entries are consumed, they are removed from the upload. This is a lower-level feature than [`consume_uploaded_entries/3`](Phoenix.LiveView.html#consume_uploaded_entries/3) and useful for scenarios where you want to consume entries as they are individually completed. Like [`consume_uploaded_entries/3`](Phoenix.LiveView.html#consume_uploaded_entries/3), the function passed to consume may return a tagged tuple of the form `{:ok, my_result}` to collect results about the consumed entries, or `{:postpone, my_result}` to collect results, but postpone the file consumption to be performed later. ## [](Phoenix.LiveView.html#consume_uploaded_entry/3-examples)Examples ``` def handle_event("save", _params, socket) do case uploaded_entries(socket, :avatar) do {[_|_] = entries, []} -> uploaded_files = for entry <- entries do consume_uploaded_entry(socket, entry, fn %{path: path} -> dest = Path.join("priv/static/uploads", Path.basename(path)) File.cp!(path, dest) {:ok, ~p"/uploads/#{Path.basename(dest)}"} end) end {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))} _ -> {:noreply, socket} end end ``` [](Phoenix.LiveView.html#detach_hook/3) # detach\_hook(socket, name, stage) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1582) Detaches a hook with the given `name` from the lifecycle `stage`. > Note: This function is for server-side lifecycle callbacks. For client-side hooks, see the [JS Interop guide](js-interop.html#client-hooks-via-phx-hook). If no hook is found, this function is a no-op. ## [](Phoenix.LiveView.html#detach_hook/3-examples)Examples ``` def handle_event(_, socket) do {:noreply, detach_hook(socket, :hook_that_was_attached, :handle_event)} end ``` [](Phoenix.LiveView.html#disallow_upload/2) # disallow\_upload(socket, name) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L848) Revokes a previously allowed upload from [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3). ## [](Phoenix.LiveView.html#disallow_upload/2-examples)Examples ``` disallow_upload(socket, :avatar) ``` [](Phoenix.LiveView.html#get_connect_info/2) # get\_connect\_info(socket, key) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1180) Accesses a given connect info key from the socket. The following keys are supported: `:peer_data`, `:trace_context_headers`, `:x_headers`, `:uri`, and `:user_agent`. The connect information is available only during mount. During disconnected render, all keys are available. On connected render, only the keys explicitly declared in your socket are available. See [`Phoenix.Endpoint.socket/3`](../phoenix/1.7.18/Phoenix.Endpoint.html#socket/3) for a complete description of the keys. ## [](Phoenix.LiveView.html#get_connect_info/2-examples)Examples The first step is to declare the `connect_info` you want to receive. Typically, it includes at least the session, but you must include all other keys you want to access on connected mount, such as `:peer_data`: ``` socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [:peer_data, session: @session_options]] ``` Those values can now be accessed on the connected mount as [`get_connect_info/2`](Phoenix.LiveView.html#get_connect_info/2): ``` def mount(_params, _session, socket) do peer_data = get_connect_info(socket, :peer_data) {:ok, assign(socket, ip: peer_data.address)} end ``` If the key is not available, usually because it was not specified in `connect_info`, it returns nil. [](Phoenix.LiveView.html#get_connect_params/1) # get\_connect\_params(socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1141) Accesses the connect params sent by the client for use on connected mount. Connect params are only sent when the client connects to the server and only remain available during mount. `nil` is returned when called in a disconnected state and a [`RuntimeError`](../elixir/RuntimeError.html) is raised if called after mount. ## [](Phoenix.LiveView.html#get_connect_params/1-reserved-params)Reserved params The following params have special meaning in LiveView: - `"_csrf_token"` - the CSRF Token which must be explicitly set by the user when connecting - `"_mounts"` - the number of times the current LiveView is mounted. It is 0 on first mount, then increases on each reconnect. It resets when navigating away from the current LiveView or on errors - `"_track_static"` - set automatically with a list of all href/src from tags with the `phx-track-static` annotation in them. If there are no such tags, nothing is sent - `"_live_referer"` - sent by the client as the referer URL when a live navigation has occurred from `push_navigate` or client link navigate. ## [](Phoenix.LiveView.html#get_connect_params/1-examples)Examples ``` def mount(_params, _session, socket) do {:ok, assign(socket, width: get_connect_params(socket)["width"] || @width)} end ``` [](Phoenix.LiveView.html#on_mount/1) # on\_mount(mod\_or\_mod\_arg) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L544)(macro) Declares a module callback to be invoked on the LiveView's mount. The function within the given module, which must be named `on_mount`, will be invoked before both disconnected and connected mounts. The hook has the option to either halt or continue the mounting process as usual. If you wish to redirect the LiveView, you **must** halt, otherwise an error will be raised. Tip: if you need to define multiple `on_mount` callbacks, avoid defining multiple modules. Instead, pass a tuple and use pattern matching to handle different cases: ``` def on_mount(:admin, _params, _session, socket) do {:cont, socket} end def on_mount(:user, _params, _session, socket) do {:cont, socket} end ``` And then invoke it as: ``` on_mount {MyAppWeb.SomeHook, :admin} on_mount {MyAppWeb.SomeHook, :user} ``` Registering `on_mount` hooks can be useful to perform authentication as well as add custom behaviour to other callbacks via [`attach_hook/4`](Phoenix.LiveView.html#attach_hook/4). The `on_mount` callback can return a keyword list of options as a third element in the return tuple. These options are identical to what can optionally be returned in [`mount/3`](Phoenix.LiveView.html#c:mount/3). ## [](Phoenix.LiveView.html#on_mount/1-examples)Examples The following is an example of attaching a hook via [`Phoenix.LiveView.Router.live_session/3`](Phoenix.LiveView.Router.html#live_session/3): ``` # lib/my_app_web/live/init_assigns.ex defmodule MyAppWeb.InitAssigns do @moduledoc """ Ensures common `assigns` are applied to all LiveViews attaching this hook. """ import Phoenix.LiveView import Phoenix.Component def on_mount(:default, _params, _session, socket) do {:cont, assign(socket, :page_title, "DemoWeb")} end def on_mount(:user, params, session, socket) do # code end def on_mount(:admin, _params, _session, socket) do {:cont, socket, layout: {DemoWeb.Layouts, :admin}} end end # lib/my_app_web/router.ex defmodule MyAppWeb.Router do use MyAppWeb, :router # pipelines, plugs, etc. live_session :default, on_mount: MyAppWeb.InitAssigns do scope "/", MyAppWeb do pipe_through :browser live "/", PageLive, :index end end live_session :authenticated, on_mount: {MyAppWeb.InitAssigns, :user} do scope "/", MyAppWeb do pipe_through [:browser, :require_user] live "/profile", UserLive.Profile, :index end end live_session :admins, on_mount: {MyAppWeb.InitAssigns, :admin} do scope "/admin", MyAppWeb.Admin do pipe_through [:browser, :require_user, :require_admin] live "/", AdminLive.Index, :index end end end ``` [](Phoenix.LiveView.html#push_event/3) # push\_event(socket, event, payload) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L775) Pushes an event to the client. Events can be handled in two ways: 1. They can be handled on `window` via `addEventListener`. A "phx:" prefix will be added to the event name. 2. They can be handled inside a hook via `handleEvent`. Events are dispatched to all active hooks on the client who are handling the given `event`. If you need to scope events, then this must be done by namespacing them. Events pushed during `push_navigate` are currently discarded, as the LiveView is immediately dismounted. ## [](Phoenix.LiveView.html#push_event/3-hook-example)Hook example If you push a "scores" event from your LiveView: ``` {:noreply, push_event(socket, "scores", %{points: 100, user: "josé"})} ``` A hook declared via `phx-hook` can handle it via `handleEvent`: ``` this.handleEvent("scores", data => ...) ``` ## [](Phoenix.LiveView.html#push_event/3-window-example)`window` example All events are also dispatched on the `window`. This means you can handle them by adding listeners. For example, if you want to remove an element from the page, you can do this: ``` {:noreply, push_event(socket, "remove-el", %{id: "foo-bar"})} ``` And now in your app.js you can register and handle it: ``` window.addEventListener( "phx:remove-el", e => document.getElementById(e.detail.id).remove() ) ``` [](Phoenix.LiveView.html#push_navigate/2) # push\_navigate(socket, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1064) Annotates the socket for navigation to another LiveView in the same `live_session`. The current LiveView will be shutdown and a new one will be mounted in its place, without reloading the whole page. This can also be used to remount the same LiveView, in case you want to start fresh. If you want to navigate to the same LiveView without remounting it, use [`push_patch/2`](Phoenix.LiveView.html#push_patch/2) instead. ## [](Phoenix.LiveView.html#push_navigate/2-options)Options - `:to` - the required path to link to. It must always be a local path - `:replace` - the flag to replace the current history or push a new state. Defaults `false`. ## [](Phoenix.LiveView.html#push_navigate/2-examples)Examples ``` {:noreply, push_navigate(socket, to: "/")} {:noreply, push_navigate(socket, to: "/", replace: true)} ``` [](Phoenix.LiveView.html#push_patch/2) # push\_patch(socket, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1038) Annotates the socket for navigation within the current LiveView. When navigating to the current LiveView, [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) is immediately invoked to handle the change of params and URL state. Then the new state is pushed to the client, without reloading the whole page while also maintaining the current scroll position. For live navigation to another LiveView in the same `live_session`, use [`push_navigate/2`](Phoenix.LiveView.html#push_navigate/2). Otherwise, use [`redirect/2`](Phoenix.LiveView.html#redirect/2). ## [](Phoenix.LiveView.html#push_patch/2-options)Options - `:to` - the required path to link to. It must always be a local path - `:replace` - the flag to replace the current history or push a new state. Defaults `false`. ## [](Phoenix.LiveView.html#push_patch/2-examples)Examples ``` {:noreply, push_patch(socket, to: "/")} {:noreply, push_patch(socket, to: "/", replace: true)} ``` [](Phoenix.LiveView.html#put_flash/3) # put\_flash(socket, kind, msg) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L688) Adds a flash message to the socket to be displayed. *Note*: While you can use [`put_flash/3`](Phoenix.LiveView.html#put_flash/3) inside a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html), components have their own `@flash` assigns. The `@flash` assign in a component is only copied to its parent LiveView if the component calls [`push_navigate/2`](Phoenix.LiveView.html#push_navigate/2) or [`push_patch/2`](Phoenix.LiveView.html#push_patch/2). *Note*: You must also place the [`Phoenix.LiveView.Router.fetch_live_flash/2`](Phoenix.LiveView.Router.html#fetch_live_flash/2) plug in your browser's pipeline in place of `fetch_flash` for LiveView flash messages be supported, for example: ``` import Phoenix.LiveView.Router pipeline :browser do ... plug :fetch_live_flash end ``` In a typical LiveView application, the message will be rendered by the CoreComponents’ flash/1 component. It is up to this function to determine what kind of messages it supports. By default, the `:info` and `:error` kinds are handled. ## [](Phoenix.LiveView.html#put_flash/3-examples)Examples ``` iex> put_flash(socket, :info, "It worked!") iex> put_flash(socket, :error, "You can't access that page") ``` [](Phoenix.LiveView.html#put_private/3) # put\_private(socket, key, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L651) Puts a new private key and value in the socket. Privates are *not change tracked*. This storage is meant to be used by users and libraries to hold state that doesn't require change tracking. The keys should be prefixed with the app/library name. ## [](Phoenix.LiveView.html#put_private/3-examples)Examples Key values can be placed in private: ``` put_private(socket, :myapp_meta, %{foo: "bar"}) ``` And then retrieved: ``` socket.private[:myapp_meta] ``` [](Phoenix.LiveView.html#redirect/2) # redirect(socket, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L973) Annotates the socket for redirect to a destination path. *Note*: LiveView redirects rely on instructing client to perform a `window.location` update on the provided redirect location. The whole page will be reloaded and all state will be discarded. ## [](Phoenix.LiveView.html#redirect/2-options)Options - `:to` - the path to redirect to. It must always be a local path - `:status` - the HTTP status code to use for the redirect. Defaults to 302. - `:external` - an external path to redirect to. Either a string or `{scheme, url}` to redirect to a custom scheme ## [](Phoenix.LiveView.html#redirect/2-examples)Examples ``` {:noreply, redirect(socket, to: "/")} {:noreply, redirect(socket, to: "/", status: 301)} {:noreply, redirect(socket, external: "https://example.com")} ``` [](Phoenix.LiveView.html#render_with/2) # render\_with(socket, component) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L619) Configures which function to use to render a LiveView/LiveComponent. By default, LiveView invokes the `render/1` function in the same module the LiveView/LiveComponent is defined, passing `assigns` as its sole argument. This function allows you to set a different rendering function. One possible use case for this function is to set a different template on disconnected render. When the user first accesses a LiveView, we will perform a disconnected render to send to the browser. This is useful for several reasons, such as reducing the time to first paint and for search engine indexing. However, when LiveView is gated behind an authentication page, it may be useful to render a placeholder on disconnected render and perform the full render once the WebSocket connects. This can be achieved with [`render_with/2`](Phoenix.LiveView.html#render_with/2) and is particularly useful on complex pages (such as dashboards and reports). To do so, you must simply invoke `render_with(socket, &some_function_component/1)`, configuring your socket with a new rendering function. [](Phoenix.LiveView.html#send_update/3) # send\_update(pid \\\\ self(), module\_or\_cid, assigns) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1374) Asynchronously updates a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) with new assigns. The `pid` argument is optional and it defaults to the current process, which means the update instruction will be sent to a component running on the same LiveView. If the current process is not a LiveView or you want to send updates to a live component running on another LiveView, you should explicitly pass the LiveView's pid instead. The second argument can be either the value of the `@myself` or the module of the live component. If you pass the module, then the `:id` that identifies the component must be passed as part of the assigns. When the component receives the update, [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) will be invoked if it is defined, otherwise [`update/2`](Phoenix.LiveComponent.html#c:update/2) is invoked with the new assigns. If [`update/2`](Phoenix.LiveComponent.html#c:update/2) is not defined all assigns are simply merged into the socket. The assigns received as the first argument of the [`update/2`](Phoenix.LiveComponent.html#c:update/2) callback will only include the *new* assigns passed from this function. Pre-existing assigns may be found in `socket.assigns`. While a component may always be updated from the parent by updating some parent assigns which will re-render the child, thus invoking [`update/2`](Phoenix.LiveComponent.html#c:update/2) on the child component, [`send_update/3`](Phoenix.LiveView.html#send_update/3) is useful for updating a component that entirely manages its own state, as well as messaging between components mounted in the same LiveView. ## [](Phoenix.LiveView.html#send_update/3-examples)Examples ``` def handle_event("cancel-order", _, socket) do ... send_update(Cart, id: "cart", status: "cancelled") {:noreply, socket} end def handle_event("cancel-order-asynchronously", _, socket) do ... pid = self() Task.Supervisor.start_child(MyTaskSup, fn -> # Do something asynchronously send_update(pid, Cart, id: "cart", status: "cancelled") end) {:noreply, socket} end def render(assigns) do ~H""" <.some_component on_complete={&send_update(@myself, completed: &1)} /> """ end ``` [](Phoenix.LiveView.html#send_update_after/4) # send\_update\_after(pid \\\\ self(), module\_or\_cid, assigns, time\_in\_milliseconds) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1417) Similar to [`send_update/3`](Phoenix.LiveView.html#send_update/3) but the update will be delayed according to the given `time_in_milliseconds`. It returns a reference which can be cancelled with [`Process.cancel_timer/1`](../elixir/Process.html#cancel_timer/1). ## [](Phoenix.LiveView.html#send_update_after/4-examples)Examples ``` def handle_event("cancel-order", _, socket) do ... send_update_after(Cart, [id: "cart", status: "cancelled"], 3000) {:noreply, socket} end def handle_event("cancel-order-asynchronously", _, socket) do ... pid = self() Task.start(fn -> # Do something asynchronously send_update_after(pid, Cart, [id: "cart", status: "cancelled"], 3000) end) {:noreply, socket} end ``` [](Phoenix.LiveView.html#start_async/4) # start\_async(socket, name, func, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L2109)(macro) Wraps your function in an asynchronous task and invokes a callback `name` to handle the result. The task is linked to the caller and errors/exits are wrapped. The result of the task is sent to the [`handle_async/3`](Phoenix.LiveView.html#c:handle_async/3) callback of the caller LiveView or LiveComponent. If there is an in-flight task with the same `name`, the later `start_async` wins and the previous task’s result is ignored. If you wish to replace an existing task, you can use [`cancel_async/3`](Phoenix.LiveView.html#cancel_async/3) before [`start_async/3`](Phoenix.LiveView.html#start_async/3). You are not restricted to just atoms for `name`, it can be any term such as a tuple. The task is only started when the socket is connected. ## [](Phoenix.LiveView.html#start_async/4-options)Options - `:supervisor` - allows you to specify a [`Task.Supervisor`](../elixir/Task.Supervisor.html) to supervise the task. ## [](Phoenix.LiveView.html#start_async/4-examples)Examples ``` def mount(%{"id" => id}, _, socket) do {:ok, socket |> assign(:org, AsyncResult.loading()) |> start_async(:my_task, fn -> fetch_org!(id) end)} end def handle_async(:my_task, {:ok, fetched_org}, socket) do %{org: org} = socket.assigns {:noreply, assign(socket, :org, AsyncResult.ok(org, fetched_org))} end def handle_async(:my_task, {:exit, reason}, socket) do %{org: org} = socket.assigns {:noreply, assign(socket, :org, AsyncResult.failed(org, {:exit, reason}))} end ``` See the moduledoc for more information. [](Phoenix.LiveView.html#static_changed?/1) # static\_changed?(socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1275) Returns true if the socket is connected and the tracked static assets have changed. This function is useful to detect if the client is running on an outdated version of the marked static files. It works by comparing the static paths sent by the client with the one on the server. **Note:** this functionality requires Phoenix v1.5.2 or later. To use this functionality, the first step is to annotate which static files you want to be tracked by LiveView, with the `phx-track-static`. For example: ``` <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /> <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}></script> ``` Now, whenever LiveView connects to the server, it will send a copy `src` or `href` attributes of all tracked statics and compare those values with the latest entries computed by [`mix phx.digest`](../phoenix/1.7.18/Mix.Tasks.Phx.Digest.html) in the server. The tracked statics on the client will match the ones on the server the huge majority of times. However, if there is a new deployment, those values may differ. You can use this function to detect those cases and show a banner to the user, asking them to reload the page. To do so, first set the assign on mount: ``` def mount(params, session, socket) do {:ok, assign(socket, static_changed?: static_changed?(socket))} end ``` And then in your views: ``` <div :if={@static_changed?} id="reload-static"> The app has been updated. Click here to <a href="#" onclick="window.location.reload()">reload</a>. </div> ``` If you prefer, you can also send a JavaScript script that immediately reloads the page. **Note:** only set `phx-track-static` on your own assets. For example, do not set it in external JavaScript files: ``` <script defer phx-track-static type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> ``` Because you don't actually serve the file above, LiveView will interpret the static above as missing, and this function will return true. [](Phoenix.LiveView.html#stream/4) # stream(socket, name, items, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1758) ``` @spec stream( %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() }, name :: atom() | String.t(), items :: Enumerable.t(), opts :: Keyword.t() ) :: %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() } ``` Assigns a new stream to the socket or inserts items into an existing stream. Returns an updated `socket`. Streams are a mechanism for managing large collections on the client without keeping the resources on the server. - `name` - A string or atom name of the key to place under the `@streams` assign. - `items` - An enumerable of items to insert. The following options are supported: - `:at` - The index to insert or update the items in the collection on the client. By default `-1` is used, which appends the items to the parent DOM container. A value of `0` prepends the items. Note that this operation is equal to inserting the items one by one, each at the given index. Therefore, when inserting multiple items at an index other than `-1`, the UI will display the items in reverse order: ``` stream(socket, :songs, [song1, song2, song3], at: 0) ``` In this case the UI will prepend `song1`, then `song2` and then `song3`, so it will show `song3`, `song2`, `song1` and then any previously inserted items. To insert in the order of the list, use [`Enum.reverse/1`](../elixir/Enum.html#reverse/1): ``` stream(socket, :songs, Enum.reverse([song1, song2, song3]), at: 0) ``` - `:reset` - A boolean to reset the stream on the client or not. Defaults to `false`. - `:limit` - An optional positive or negative number of results to limit on the UI on the client. As new items are streamed, the UI will remove existing items to maintain the limit. For example, to limit the stream to the last 10 items in the UI while appending new items, pass a negative value: ``` stream(socket, :songs, songs, at: -1, limit: -10) ``` Likewise, to limit the stream to the first 10 items, while prepending new items, pass a positive value: ``` stream(socket, :songs, songs, at: 0, limit: 10) ``` Once a stream is defined, a new `@streams` assign is available containing the name of the defined streams. For example, in the above definition, the stream may be referenced as `@streams.songs` in your template. Stream items are temporary and freed from socket state immediately after the `render/1` function is invoked (or a template is rendered from disk). By default, calling [`stream/4`](Phoenix.LiveView.html#stream/4) on an existing stream will bulk insert the new items on the client while leaving the existing items in place. Streams may also be reset when calling [`stream/4`](Phoenix.LiveView.html#stream/4), which we discuss below. ## [](Phoenix.LiveView.html#stream/4-resetting-a-stream)Resetting a stream To empty a stream container on the client, you can pass `:reset` with an empty list: ``` stream(socket, :songs, [], reset: true) ``` Or you can replace the entire stream on the client with a new collection: ``` stream(socket, :songs, new_songs, reset: true) ``` ## [](Phoenix.LiveView.html#stream/4-limiting-a-stream)Limiting a stream It is often useful to limit the number of items in the UI while allowing the server to stream new items in a fire-and-forget fashion. This prevents the server from overwhelming the client with new results while also opening up powerful features like virtualized infinite scrolling. See a complete bidirectional infinite scrolling example with stream limits in the [scroll events guide](bindings.html#scroll-events-and-infinite-stream-pagination) When a stream exceeds the limit on the client, the existing items will be pruned based on the number of items in the stream container and the limit direction. A positive limit will prune items from the end of the container, while a negative limit will prune items from the beginning of the container. Note that the limit is not enforced on the first [`mount/3`](Phoenix.LiveView.html#c:mount/3) render (when no websocket connection was established yet), as it means more data than necessary has been loaded. In such cases, you should only load and pass the desired amount of items to the stream. When inserting single items using [`stream_insert/4`](Phoenix.LiveView.html#stream_insert/4), the limit needs to be passed as an option for it to be enforced on the client: ``` stream_insert(socket, :songs, song, limit: -10) ``` ## [](Phoenix.LiveView.html#stream/4-required-dom-attributes)Required DOM attributes For stream items to be trackable on the client, the following requirements must be met: 1. The parent DOM container must include a `phx-update="stream"` attribute, along with a unique DOM id. 2. Each stream item must include its DOM id on the item's element. #### Note Failing to place `phx-update="stream"` on the **immediate parent** for **each stream** will result in broken behavior. Also, do not alter the generated DOM ids, e.g., by prefixing them. Doing so will result in broken behavior. When consuming a stream in a template, the DOM id and item is passed as a tuple, allowing convenient inclusion of the DOM id for each item. For example: ``` <table> <tbody id="songs" phx-update="stream"> <tr :for={{dom_id, song} <- @streams.songs} id={dom_id} > <td>{song.title}</td> <td>{song.duration}</td> </tr> </tbody> </table> ``` We consume the stream in a for comprehension by referencing the `@streams.songs` assign. We used the computed DOM id to populate the `<tr>` id, then we render the table row as usual. Now [`stream_insert/3`](Phoenix.LiveView.html#stream_insert/3) and [`stream_delete/3`](Phoenix.LiveView.html#stream_delete/3) may be issued and new rows will be inserted or deleted from the client. ## [](Phoenix.LiveView.html#stream/4-handling-the-empty-case)Handling the empty case When rendering a list of items, it is common to show a message for the empty case. But when using streams, we cannot rely on [`Enum.empty?/1`](../elixir/Enum.html#empty?/1) or similar approaches to check if the list is empty. Instead we can use the CSS `:only-child` selector and show the message client side: ``` <table> <tbody id="songs" phx-update="stream"> <tr id="songs-empty" class="only:block hidden"> <td colspan="2">No songs found</td> </tr> <tr :for={{dom_id, song} <- @streams.songs} id={dom_id} > <td>{song.title}</td> <td>{song.duration}</td> </tr> </tbody> </table> ``` ## [](Phoenix.LiveView.html#stream/4-non-stream-items-in-stream-containers)Non-stream items in stream containers In the section on handling the empty case, we showed how to render a message when the stream is empty by rendering a non-stream item inside the stream container. Note that for non-stream items inside a `phx-update="stream"` container, the following needs to be considered: 1. Items can be added and updated, but not removed, even if the stream is reset. This means that if you try to conditionally render a non-stream item inside a stream container, it won't be removed if it was rendered once. 2. Items are affected by the `:at` option. For example, when you render a non-stream item at the beginning of the stream container and then prepend items (with `at: 0`) to the stream, the non-stream item will be pushed down. [](Phoenix.LiveView.html#stream_configure/3) # stream\_configure(socket, name, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1796) ``` @spec stream_configure( %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() }, name :: atom() | String.t(), opts :: Keyword.t() ) :: %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() } ``` Configures a stream. The following options are supported: - `:dom_id` - An optional function to generate each stream item's DOM id. The function accepts each stream item and converts the item to a string id. By default, the `:id` field of a map or struct will be used if the item has such a field, and will be prefixed by the `name` hyphenated with the id. For example, the following examples are equivalent: ``` stream(socket, :songs, songs) socket |> stream_configure(:songs, dom_id: &("songs-#{&1.id}")) |> stream(:songs, songs) ``` A stream must be configured before items are inserted, and once configured, a stream may not be re-configured. To ensure a stream is only configured a single time in a LiveComponent, use the `mount/1` callback. For example: ``` def mount(socket) do {:ok, stream_configure(socket, :songs, dom_id: &("songs-#{&1.id}"))} end def update(assigns, socket) do {:ok, stream(socket, :songs, ...)} end ``` Returns an updated `socket`. [](Phoenix.LiveView.html#stream_delete/3) # stream\_delete(socket, name, item) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1908) ``` @spec stream_delete( %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() }, name :: atom() | String.t(), item :: any() ) :: %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() } ``` Deletes an item from the stream. The item's DOM is computed from the `:dom_id` provided in the [`stream/3`](Phoenix.LiveView.html#stream/3) definition. Delete information for this DOM id is sent to the client and the item's element is removed from the DOM, following the same behavior of element removal, such as invoking `phx-remove` commands and executing client hook `destroyed()` callbacks. ## [](Phoenix.LiveView.html#stream_delete/3-examples)Examples ``` def handle_event("delete", %{"id" => id}, socket) do song = get_song!(id) {:noreply, stream_delete(socket, :songs, song)} end ``` See [`stream_delete_by_dom_id/3`](Phoenix.LiveView.html#stream_delete_by_dom_id/3) to remove an item without requiring the original data structure. Returns an updated `socket`. [](Phoenix.LiveView.html#stream_delete_by_dom_id/3) # stream\_delete\_by\_dom\_id(socket, name, id) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1945) ``` @spec stream_delete_by_dom_id( %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() }, name :: atom() | String.t(), id :: String.t() ) :: %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() } ``` Deletes an item from the stream given its computed DOM id. Returns an updated `socket`. Behaves just like [`stream_delete/3`](Phoenix.LiveView.html#stream_delete/3), but accept the precomputed DOM id, which allows deleting from a stream without fetching or building the original stream data structure. ## [](Phoenix.LiveView.html#stream_delete_by_dom_id/3-examples)Examples ``` def render(assigns) do ~H""" <table> <tbody id="songs" phx-update="stream"> <tr :for={{dom_id, song} <- @streams.songs} id={dom_id} > <td>{song.title}</td> <td><button phx-click={JS.push("delete", value: %{id: dom_id})}>delete</button></td> </tr> </tbody> </table> """ end def handle_event("delete", %{"id" => dom_id}, socket) do {:noreply, stream_delete_by_dom_id(socket, :songs, dom_id)} end ``` [](Phoenix.LiveView.html#stream_insert/4) # stream\_insert(socket, name, item, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1880) ``` @spec stream_insert( %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() }, name :: atom() | String.t(), item :: any(), opts :: Keyword.t() ) :: %Phoenix.LiveView.Socket{ assigns: term(), endpoint: term(), fingerprints: term(), host_uri: term(), id: term(), parent_pid: term(), private: term(), redirected: term(), root_pid: term(), router: term(), transport_pid: term(), view: term() } ``` Inserts a new item or updates an existing item in the stream. Returns an updated `socket`. See [`stream/4`](Phoenix.LiveView.html#stream/4) for inserting multiple items at once. The following options are supported: - `:at` - The index to insert or update the item in the collection on the client. By default, the item is appended to the parent DOM container. This is the same as passing a value of `-1`. If the item already exists in the parent DOM container then it will be updated in place. - `:limit` - A limit of items to maintain in the UI. A limit passed to [`stream/4`](Phoenix.LiveView.html#stream/4) does not affect subsequent calls to [`stream_insert/4`](Phoenix.LiveView.html#stream_insert/4), therefore the limit must be passed here as well in order to be enforced. See [`stream/4`](Phoenix.LiveView.html#stream/4) for more information on limiting streams. ## [](Phoenix.LiveView.html#stream_insert/4-examples)Examples Imagine you define a stream on mount with a single item: ``` stream(socket, :songs, [%Song{id: 1, title: "Song 1"}]) ``` Then, in a callback such as `handle_info` or `handle_event`, you can append a new song: ``` stream_insert(socket, :songs, %Song{id: 2, title: "Song 2"}) ``` Or prepend a new song with `at: 0`: ``` stream_insert(socket, :songs, %Song{id: 2, title: "Song 2"}, at: 0) ``` Or update an existing song (in this case the `:at` option has no effect): ``` stream_insert(socket, :songs, %Song{id: 1, title: "Song 1 updated"}, at: 0) ``` Or append a new song while limiting the stream to the last 10 items: ``` stream_insert(socket, :songs, %Song{id: 2, title: "Song 2"}, limit: -10) ``` ## [](Phoenix.LiveView.html#stream_insert/4-updating-items)Updating Items As shown, an existing item on the client can be updated by issuing a `stream_insert` for the existing item. When the client updates an existing item, the item will remain in the same location as it was previously, and will not be moved to the end of the parent children. To both update an existing item and move it to another position, issue a `stream_delete`, followed by a `stream_insert`. For example: ``` song = get_song!(id) socket |> stream_delete(:songs, song) |> stream_insert(:songs, song, at: -1) ``` See [`stream_delete/3`](Phoenix.LiveView.html#stream_delete/3) for more information on deleting items. [](Phoenix.LiveView.html#transport_pid/1) # transport\_pid(socket) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L1447) Returns the transport pid of the socket. Raises [`ArgumentError`](../elixir/ArgumentError.html) if the socket is not connected. ## [](Phoenix.LiveView.html#transport_pid/1-examples)Examples ``` iex> transport_pid(socket) #PID<0.107.0> ``` [](Phoenix.LiveView.html#uploaded_entries/2) # uploaded\_entries(socket, name) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view.ex#L881) Returns the completed and in progress entries for the upload. ## [](Phoenix.LiveView.html#uploaded_entries/2-examples)Examples ``` case uploaded_entries(socket, :photos) do {[_ | _] = completed, []} -> # all entries are completed {[], [_ | _] = in_progress} -> # all entries are still in progress end ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveViewTest.Element.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/structs.ex#L25 "View Source") Phoenix.LiveViewTest.Element (Phoenix LiveView v1.0.2) The struct returned by [`Phoenix.LiveViewTest.element/3`](Phoenix.LiveViewTest.html#element/3). The following public fields represent the element: - `selector` - The query selector - `text_filter` - The text to further filter the element See the [`Phoenix.LiveViewTest`](Phoenix.LiveViewTest.html) documentation for usage. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveViewTest.Upload.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/structs.ex#L46 "View Source") Phoenix.LiveViewTest.Upload (Phoenix LiveView v1.0.2) The struct returned by [`Phoenix.LiveViewTest.file_input/4`](Phoenix.LiveViewTest.html#file_input/4). The following public fields represent the element: - `selector` - The query selector - `entries` - The list of selected file entries See the [`Phoenix.LiveViewTest`](Phoenix.LiveViewTest.html) documentation for usage. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveViewTest.View.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/structs.ex#L1 "View Source") Phoenix.LiveViewTest.View (Phoenix LiveView v1.0.2) The struct for testing LiveViews. The following public fields represent the LiveView: - `id` - The DOM id of the LiveView - `module` - The module of the running LiveView - `pid` - The Pid of the running LiveView - `endpoint` - The endpoint for the LiveView - `target` - The target to scope events to See the [`Phoenix.LiveViewTest`](Phoenix.LiveViewTest.html) documentation for usage. [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/Phoenix.LiveViewTest.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1 "View Source") Phoenix.LiveViewTest (Phoenix LiveView v1.0.2) Conveniences for testing function components as well as LiveViews and LiveComponents. ## [](Phoenix.LiveViewTest.html#module-testing-function-components)Testing function components There are two mechanisms for testing function components. Imagine the following component: ``` def greet(assigns) do ~H""" <div>Hello, {@name}!</div> """ end ``` You can test it by using [`render_component/3`](Phoenix.LiveViewTest.html#render_component/3), passing the function reference to the component as first argument: ``` import Phoenix.LiveViewTest test "greets" do assert render_component(&MyComponents.greet/1, name: "Mary") == "<div>Hello, Mary!</div>" end ``` However, for complex components, often the simplest way to test them is by using the `~H` sigil itself: ``` import Phoenix.Component import Phoenix.LiveViewTest test "greets" do assigns = %{} assert rendered_to_string(~H""" <MyComponents.greet name="Mary" /> """) == "<div>Hello, Mary!</div>" end ``` The difference is that we use [`rendered_to_string/1`](Phoenix.LiveViewTest.html#rendered_to_string/1) to convert the rendered template to a string for testing. ## [](Phoenix.LiveViewTest.html#module-testing-liveviews-and-livecomponents)Testing LiveViews and LiveComponents In LiveComponents and LiveView tests, we interact with views via process communication in substitution of a browser. Like a browser, our test process receives messages about the rendered updates from the view which can be asserted against to test the life-cycle and behavior of LiveViews and their children. ### [](Phoenix.LiveViewTest.html#module-testing-liveviews)Testing LiveViews The life-cycle of a LiveView as outlined in the [`Phoenix.LiveView`](Phoenix.LiveView.html) docs details how a view starts as a stateless HTML render in a disconnected socket state. Once the browser receives the HTML, it connects to the server and a new LiveView process is started, remounted in a connected socket state, and the view continues statefully. The LiveView test functions support testing both disconnected and connected mounts separately, for example: ``` import Plug.Conn import Phoenix.ConnTest import Phoenix.LiveViewTest @endpoint MyEndpoint test "disconnected and connected mount", %{conn: conn} do conn = get(conn, "/my-path") assert html_response(conn, 200) =~ "<h1>My Disconnected View</h1>" {:ok, view, html} = live(conn) end test "redirected mount", %{conn: conn} do assert {:error, {:redirect, %{to: "/somewhere"}}} = live(conn, "my-path") end ``` Here, we start by using the familiar [`Phoenix.ConnTest`](../phoenix/1.7.18/Phoenix.ConnTest.html) function, `get/2` to test the regular HTTP GET request which invokes mount with a disconnected socket. Next, [`live/1`](Phoenix.LiveViewTest.html#live/1) is called with our sent connection to mount the view in a connected state, which starts our stateful LiveView process. In general, it's often more convenient to test the mounting of a view in a single step, provided you don't need the result of the stateless HTTP render. This is done with a single call to [`live/2`](Phoenix.LiveViewTest.html#live/2), which performs the `get` step for us: ``` test "connected mount", %{conn: conn} do {:ok, _view, html} = live(conn, "/my-path") assert html =~ "<h1>My Connected View</h1>" end ``` ### [](Phoenix.LiveViewTest.html#module-testing-events)Testing Events The browser can send a variety of events to a LiveView via `phx-` bindings, which are sent to the `handle_event/3` callback. To test events sent by the browser and assert on the rendered side effect of the event, use the `render_*` functions: - [`render_click/1`](Phoenix.LiveViewTest.html#render_click/1) - sends a phx-click event and value, returning the rendered result of the `handle_event/3` callback. - [`render_focus/2`](Phoenix.LiveViewTest.html#render_focus/2) - sends a phx-focus event and value, returning the rendered result of the `handle_event/3` callback. - [`render_blur/1`](Phoenix.LiveViewTest.html#render_blur/1) - sends a phx-blur event and value, returning the rendered result of the `handle_event/3` callback. - [`render_submit/1`](Phoenix.LiveViewTest.html#render_submit/1) - sends a form phx-submit event and value, returning the rendered result of the `handle_event/3` callback. - [`render_change/1`](Phoenix.LiveViewTest.html#render_change/1) - sends a form phx-change event and value, returning the rendered result of the `handle_event/3` callback. - [`render_keydown/1`](Phoenix.LiveViewTest.html#render_keydown/1) - sends a form phx-keydown event and value, returning the rendered result of the `handle_event/3` callback. - [`render_keyup/1`](Phoenix.LiveViewTest.html#render_keyup/1) - sends a form phx-keyup event and value, returning the rendered result of the `handle_event/3` callback. - [`render_hook/3`](Phoenix.LiveViewTest.html#render_hook/3) - sends a hook event and value, returning the rendered result of the `handle_event/3` callback. For example: ``` {:ok, view, _html} = live(conn, "/thermo") assert view |> element("button#inc") |> render_click() =~ "The temperature is: 31℉" ``` In the example above, we are looking for a particular element on the page and triggering its phx-click event. LiveView takes care of making sure the element has a phx-click and automatically sends its values to the server. You can also bypass the element lookup and directly trigger the LiveView event in most functions: ``` assert render_click(view, :inc, %{}) =~ "The temperature is: 31℉" ``` The `element` style is preferred as much as possible, as it helps LiveView perform validations and ensure the events in the HTML actually matches the event names on the server. ### [](Phoenix.LiveViewTest.html#module-testing-regular-messages)Testing regular messages LiveViews are [`GenServer`](../elixir/GenServer.html)'s under the hood, and can send and receive messages just like any other server. To test the side effects of sending or receiving messages, simply message the view and use the `render` function to test the result: ``` send(view.pid, {:set_temp, 50}) assert render(view) =~ "The temperature is: 50℉" ``` ### [](Phoenix.LiveViewTest.html#module-testing-livecomponents)Testing LiveComponents LiveComponents can be tested in two ways. One way is to use the same [`render_component/2`](Phoenix.LiveViewTest.html#render_component/2) function as function components. This will mount the LiveComponent and render it once, without testing any of its events: ``` assert render_component(MyComponent, id: 123, user: %User{}) =~ "some markup in component" ``` However, if you want to test how components are mounted by a LiveView and interact with DOM events, you must use the regular [`live/2`](Phoenix.LiveViewTest.html#live/2) macro to build the LiveView with the component and then scope events by passing the view and a **DOM selector** in a list: ``` {:ok, view, html} = live(conn, "/users") html = view |> element("#user-13 a", "Delete") |> render_click() refute html =~ "user-13" refute view |> element("#user-13") |> has_element?() ``` In the example above, LiveView will lookup for an element with ID=user-13 and retrieve its `phx-target`. If `phx-target` points to a component, that will be the component used, otherwise it will fallback to the view. # [](Phoenix.LiveViewTest.html#summary)Summary ## [Functions](Phoenix.LiveViewTest.html#functions) [assert\_patch(view, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.LiveViewTest.html#assert_patch/2) Asserts a live patch will happen within `timeout` milliseconds. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). [assert\_patch(view, to, timeout)](Phoenix.LiveViewTest.html#assert_patch/3) Asserts a live patch will happen to a given path within `timeout` milliseconds. [assert\_patched(view, to)](Phoenix.LiveViewTest.html#assert_patched/2) Asserts a live patch was performed, and returns the new path. [assert\_push\_event(view, event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.LiveViewTest.html#assert_push_event/4) Asserts an event will be pushed within `timeout`. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). [assert\_redirect(view, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.LiveViewTest.html#assert_redirect/2) Asserts a redirect will happen within `timeout` milliseconds. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). [assert\_redirect(view, to, timeout)](Phoenix.LiveViewTest.html#assert_redirect/3) Asserts a redirect will happen to a given path within `timeout` milliseconds. [assert\_redirected(view, to)](Phoenix.LiveViewTest.html#assert_redirected/2) Asserts a redirect was performed. [assert\_reply(view, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.LiveViewTest.html#assert_reply/3) Asserts a hook reply was returned from a `handle_event` callback. [element(view, selector, text\_filter \\\\ nil)](Phoenix.LiveViewTest.html#element/3) Returns an element to scope a function to. [file\_input(view, form\_selector, name, entries)](Phoenix.LiveViewTest.html#file_input/4) Builds a file input for testing uploads within a form. [find\_live\_child(parent, child\_id)](Phoenix.LiveViewTest.html#find_live_child/2) Gets the nested LiveView child by `child_id` from the `parent` LiveView. [follow\_redirect(reason, conn, to \\\\ nil)](Phoenix.LiveViewTest.html#follow_redirect/3) Follows the redirect from a `render_*` action or an `{:error, redirect}` tuple. [follow\_trigger\_action(form, conn)](Phoenix.LiveViewTest.html#follow_trigger_action/2) Receives a `form_element` and asserts that `phx-trigger-action` has been set to true, following up on that request. [form(view, selector, form\_data \\\\ %{})](Phoenix.LiveViewTest.html#form/3) Returns a form element to scope a function to. [has\_element?(element)](Phoenix.LiveViewTest.html#has_element?/1) Checks if the given element exists on the page. [has\_element?(view, selector, text\_filter \\\\ nil)](Phoenix.LiveViewTest.html#has_element?/3) Checks if the given `selector` with `text_filter` is on `view`. [live(conn, path \\\\ nil)](Phoenix.LiveViewTest.html#live/2) Spawns a connected LiveView process. [live\_children(parent)](Phoenix.LiveViewTest.html#live_children/1) Returns the current list of LiveView children for the `parent` LiveView. [live\_isolated(conn, live\_view, opts \\\\ \[\])](Phoenix.LiveViewTest.html#live_isolated/3) Spawns a connected LiveView process mounted in isolation as the sole rendered element. [live\_redirect(view, opts)](Phoenix.LiveViewTest.html#live_redirect/2) Performs a live redirect from one LiveView to another. [open\_browser(view\_or\_element, open\_fun \\\\ &amp;open\_with\_system\_cmd/1)](Phoenix.LiveViewTest.html#open_browser/2) Open the default browser to display current HTML of `view_or_element`. [page\_title(view)](Phoenix.LiveViewTest.html#page_title/1) Returns the most recent title that was updated via a `page_title` assign. [preflight\_upload(upload)](Phoenix.LiveViewTest.html#preflight_upload/1) Performs a preflight upload request. [put\_connect\_params(conn, params)](Phoenix.LiveViewTest.html#put_connect_params/2) Puts connect params to be used on LiveView connections. [put\_submitter(form, element\_or\_selector)](Phoenix.LiveViewTest.html#put_submitter/2) Puts the submitter `element_or_selector` on the given `form` element. [refute\_push\_event(view, event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout))](Phoenix.LiveViewTest.html#refute_push_event/4) Refutes an event will be pushed within timeout. [refute\_redirected(view, to)](Phoenix.LiveViewTest.html#refute_redirected/2) Refutes a redirect to a given path was performed. [render(view\_or\_element)](Phoenix.LiveViewTest.html#render/1) Returns the HTML string of the rendered view or element. [render\_async(view\_or\_element, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout))](Phoenix.LiveViewTest.html#render_async/2) Awaits all current `assign_async` and `start_async` for a given LiveView or element. [render\_blur(element, value \\\\ %{})](Phoenix.LiveViewTest.html#render_blur/2) Sends a blur event given by `element` and returns the rendered result. [render\_blur(view, event, value)](Phoenix.LiveViewTest.html#render_blur/3) Sends a blur event to the view and returns the rendered result. [render\_change(element, value \\\\ %{})](Phoenix.LiveViewTest.html#render_change/2) Sends a form change event given by `element` and returns the rendered result. [render\_change(view, event, value)](Phoenix.LiveViewTest.html#render_change/3) Sends a form change event to the view and returns the rendered result. [render\_click(element, value \\\\ %{})](Phoenix.LiveViewTest.html#render_click/2) Sends a click event given by `element` and returns the rendered result. [render\_click(view, event, value)](Phoenix.LiveViewTest.html#render_click/3) Sends a click `event` to the `view` with `value` and returns the rendered result. [render\_component(component, assigns \\\\ Macro.escape(%{}), opts \\\\ \[\])](Phoenix.LiveViewTest.html#render_component/3) Renders a component. [render\_focus(element, value \\\\ %{})](Phoenix.LiveViewTest.html#render_focus/2) Sends a focus event given by `element` and returns the rendered result. [render\_focus(view, event, value)](Phoenix.LiveViewTest.html#render_focus/3) Sends a focus event to the view and returns the rendered result. [render\_hook(view\_or\_element, event, value \\\\ %{})](Phoenix.LiveViewTest.html#render_hook/3) Sends a hook event to the view or an element and returns the rendered result. [render\_keydown(element, value \\\\ %{})](Phoenix.LiveViewTest.html#render_keydown/2) Sends a keydown event given by `element` and returns the rendered result. [render\_keydown(view, event, value)](Phoenix.LiveViewTest.html#render_keydown/3) Sends a keydown event to the view and returns the rendered result. [render\_keyup(element, value \\\\ %{})](Phoenix.LiveViewTest.html#render_keyup/2) Sends a keyup event given by `element` and returns the rendered result. [render\_keyup(view, event, value)](Phoenix.LiveViewTest.html#render_keyup/3) Sends a keyup event to the view and returns the rendered result. [render\_patch(view, path)](Phoenix.LiveViewTest.html#render_patch/2) Simulates a `push_patch` to the given `path` and returns the rendered result. [render\_submit(element, value \\\\ %{})](Phoenix.LiveViewTest.html#render_submit/2) Sends a form submit event given by `element` and returns the rendered result. [render\_submit(view, event, value)](Phoenix.LiveViewTest.html#render_submit/3) Sends a form submit event to the view and returns the rendered result. [render\_upload(upload, entry\_name, percent \\\\ 100)](Phoenix.LiveViewTest.html#render_upload/3) Performs an upload of a file input and renders the result. [rendered\_to\_string(rendered)](Phoenix.LiveViewTest.html#rendered_to_string/1) Converts a rendered template to a string. [submit\_form(form, conn)](Phoenix.LiveViewTest.html#submit_form/2) Receives a form element and submits the HTTP request through the plug pipeline. [with\_target(view, target)](Phoenix.LiveViewTest.html#with_target/2) Sets the target of the view for events. # [](Phoenix.LiveViewTest.html#functions)Functions [](Phoenix.LiveViewTest.html#assert_patch/2) # assert\_patch(view, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1298) Asserts a live patch will happen within `timeout` milliseconds. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). It returns the new path. To assert on the flash message, you can assert on the result of the rendered LiveView. ## [](Phoenix.LiveViewTest.html#assert_patch/2-examples)Examples ``` render_click(view, :event_that_triggers_patch) assert_patch view render_click(view, :event_that_triggers_patch) assert_patch view, 30 render_click(view, :event_that_triggers_patch) path = assert_patch view assert path =~ ~r/path/+/ ``` [](Phoenix.LiveViewTest.html#assert_patch/3) # assert\_patch(view, to, timeout) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1329) Asserts a live patch will happen to a given path within `timeout` milliseconds. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). It returns the new path. To assert on the flash message, you can assert on the result of the rendered LiveView. ## [](Phoenix.LiveViewTest.html#assert_patch/3-examples)Examples ``` render_click(view, :event_that_triggers_patch) assert_patch view, "/path" render_click(view, :event_that_triggers_patch) assert_patch view, "/path", 30 ``` [](Phoenix.LiveViewTest.html#assert_patched/2) # assert\_patched(view, to) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1347) Asserts a live patch was performed, and returns the new path. To assert on the flash message, you can assert on the result of the rendered LiveView. ## [](Phoenix.LiveViewTest.html#assert_patched/2-examples)Examples ``` render_click(view, :event_that_triggers_redirect) assert_patched view, "/path" ``` [](Phoenix.LiveViewTest.html#assert_push_event/4) # assert\_push\_event(view, event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1603)(macro) Asserts an event will be pushed within `timeout`. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). ## [](Phoenix.LiveViewTest.html#assert_push_event/4-examples)Examples ``` assert_push_event view, "scores", %{points: 100, user: "josé"} ``` [](Phoenix.LiveViewTest.html#assert_redirect/2) # assert\_redirect(view, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1369) Asserts a redirect will happen within `timeout` milliseconds. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). It returns a tuple containing the new path and the flash messages from said redirect, if any. Note the flash will contain string keys. ## [](Phoenix.LiveViewTest.html#assert_redirect/2-examples)Examples ``` render_click(view, :event_that_triggers_redirect) {path, flash} = assert_redirect view assert flash["info"] == "Welcome" assert path =~ ~r/path\/\d+/ render_click(view, :event_that_triggers_redirect) assert_redirect view, 30 ``` [](Phoenix.LiveViewTest.html#assert_redirect/3) # assert\_redirect(view, to, timeout) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1397) Asserts a redirect will happen to a given path within `timeout` milliseconds. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). It returns the flash messages from said redirect, if any. Note the flash will contain string keys. ## [](Phoenix.LiveViewTest.html#assert_redirect/3-examples)Examples ``` render_click(view, :event_that_triggers_redirect) flash = assert_redirect view, "/path" assert flash["info"] == "Welcome" render_click(view, :event_that_triggers_redirect) assert_redirect view, "/path", 30 ``` [](Phoenix.LiveViewTest.html#assert_redirected/2) # assert\_redirected(view, to) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1416) Asserts a redirect was performed. It returns the flash messages from said redirect, if any. Note the flash will contain string keys. ## [](Phoenix.LiveViewTest.html#assert_redirected/2-examples)Examples ``` render_click(view, :event_that_triggers_redirect) flash = assert_redirected view, "/path" assert flash["info"] == "Welcome" ``` [](Phoenix.LiveViewTest.html#assert_reply/3) # assert\_reply(view, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1661)(macro) Asserts a hook reply was returned from a `handle_event` callback. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). ## [](Phoenix.LiveViewTest.html#assert_reply/3-examples)Examples ``` assert_reply view, %{result: "ok", transaction_id: _} ``` [](Phoenix.LiveViewTest.html#element/3) # element(view, selector, text\_filter \\\\ nil) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1143) Returns an element to scope a function to. It expects the current LiveView, a query selector, and a text filter. An optional text filter may be given to filter the results by the query selector. If the text filter is a string or a regex, it will match any element that contains the string (including as a substring) or matches the regex. So a link containing the text "unopened" will match `element("a", "opened")`. To prevent this, a regex could specify that "opened" appear without the prefix "un". For example, `element("a", ~r{(?<!un)opened})`. But it may be clearer to add an HTML attribute to make the element easier to select. After the text filter is applied, only one element must remain, otherwise an error is raised. If no text filter is given, then the query selector itself must return a single element. ``` assert view |> element("#term > :first-child", "Increment") |> render() =~ "Increment</a>" ``` Attribute selectors are also supported, and may be used on special cases like ids which contain periods: ``` assert view |> element(~s{[href="/foo"][id="foo.bar.baz"]}) |> render() =~ "Increment</a>" ``` [](Phoenix.LiveViewTest.html#file_input/4) # file\_input(view, form\_selector, name, entries) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1200)(macro) Builds a file input for testing uploads within a form. Given the form DOM selector, the upload name, and a list of maps of client metadata for the upload, the returned file input can be passed to [`render_upload/2`](Phoenix.LiveViewTest.html#render_upload/2). Client metadata takes the following form: - `:last_modified` - the last modified timestamp - `:name` - the name of the file - `:content` - the binary content of the file - `:size` - the byte size of the content - `:type` - the MIME type of the file - `:relative_path` - for simulating webkitdirectory metadata - `:meta` - optional metadata sent by the client ## [](Phoenix.LiveViewTest.html#file_input/4-examples)Examples ``` avatar = file_input(lv, "#my-form-id", :avatar, [%{ last_modified: 1_594_171_879_000, name: "myfile.jpeg", content: File.read!("myfile.jpg"), size: 1_396_009, type: "image/jpeg" }]) assert render_upload(avatar, "myfile.jpeg") =~ "100%" ``` [](Phoenix.LiveViewTest.html#find_live_child/2) # find\_live\_child(parent, child\_id) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1007) Gets the nested LiveView child by `child_id` from the `parent` LiveView. ## [](Phoenix.LiveViewTest.html#find_live_child/2-examples)Examples ``` {:ok, view, _html} = live(conn, "/thermo") assert clock_view = find_live_child(view, "clock") assert render_click(clock_view, :snooze) =~ "snoozing" ``` [](Phoenix.LiveViewTest.html#follow_redirect/3) # follow\_redirect(reason, conn, to \\\\ nil) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1708)(macro) Follows the redirect from a `render_*` action or an `{:error, redirect}` tuple. Imagine you have a LiveView that redirects on a `render_click` event. You can make sure it immediately redirects after the `render_click` action by calling [`follow_redirect/3`](Phoenix.LiveViewTest.html#follow_redirect/3): ``` live_view |> render_click("redirect") |> follow_redirect(conn) ``` Or in the case of an error tuple: ``` assert {:error, {:redirect, %{to: "/somewhere"}}} = result = live(conn, "my-path") {:ok, view, html} = follow_redirect(result, conn) ``` [`follow_redirect/3`](Phoenix.LiveViewTest.html#follow_redirect/3) expects a connection as second argument. This is the connection that will be used to perform the underlying request. If the LiveView redirects with a live redirect, this macro returns `{:ok, live_view, disconnected_html}` with the content of the new LiveView, the same as the `live/3` macro. If the LiveView redirects with a regular redirect, this macro returns `{:ok, conn}` with the rendered redirected page. In any other case, this macro raises. Finally, note that you can optionally assert on the path you are being redirected to by passing a third argument: ``` live_view |> render_click("redirect") |> follow_redirect(conn, "/redirected/page") ``` [](Phoenix.LiveViewTest.html#follow_trigger_action/2) # follow\_trigger\_action(form, conn) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1829)(macro) Receives a `form_element` and asserts that `phx-trigger-action` has been set to true, following up on that request. Imagine you have a LiveView that sends an HTTP form submission. Say that it sets the `phx-trigger-action` to true, as a response to a submit event. You can follow the trigger action like this: ``` form = form(live_view, selector, %{"form" => "data"}) # First we submit the form. Optionally verify that phx-trigger-action # is now part of the form. assert render_submit(form) =~ ~r/phx-trigger-action/ # Now follow the request made by the form conn = follow_trigger_action(form, conn) assert conn.method == "POST" assert conn.params == %{"form" => "data"} ``` [](Phoenix.LiveViewTest.html#form/3) # form(view, selector, form\_data \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1168) Returns a form element to scope a function to. It expects the current LiveView, a query selector, and the form data. The query selector must return a single element. The form data will be validated directly against the form markup and make sure the data you are changing/submitting actually exists, failing otherwise. ## [](Phoenix.LiveViewTest.html#form/3-examples)Examples ``` assert view |> form("#term", user: %{name: "hello"}) |> render_submit() =~ "Name updated" ``` This function is meant to mimic what the user can actually do, so you cannot set hidden input values. However, hidden values can be given when calling [`render_submit/2`](Phoenix.LiveViewTest.html#render_submit/2) or [`render_change/2`](Phoenix.LiveViewTest.html#render_change/2), see their docs for examples. [](Phoenix.LiveViewTest.html#has_element?/1) # has\_element?(element) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1021) Checks if the given element exists on the page. ## [](Phoenix.LiveViewTest.html#has_element?/1-examples)Examples ``` assert view |> element("#some-element") |> has_element?() ``` [](Phoenix.LiveViewTest.html#has_element?/3) # has\_element?(view, selector, text\_filter \\\\ nil) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1038) Checks if the given `selector` with `text_filter` is on `view`. See [`element/3`](Phoenix.LiveViewTest.html#element/3) for more information. ## [](Phoenix.LiveViewTest.html#has_element?/3-examples)Examples ``` assert has_element?(view, "#some-element") ``` [](Phoenix.LiveViewTest.html#live/2) # live(conn, path \\\\ nil) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L215)(macro) Spawns a connected LiveView process. If a `path` is given, then a regular `get(conn, path)` is done and the page is upgraded to a LiveView. If no path is given, it assumes a previously rendered `%Plug.Conn{}` is given, which will be converted to a LiveView immediately. ## [](Phoenix.LiveViewTest.html#live/2-examples)Examples ``` {:ok, view, html} = live(conn, "/path") assert view.module == MyLive assert html =~ "the count is 3" assert {:error, {:redirect, %{to: "/somewhere"}}} = live(conn, "/path") ``` [](Phoenix.LiveViewTest.html#live_children/1) # live\_children(parent) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L994) Returns the current list of LiveView children for the `parent` LiveView. Children are returned in the order they appear in the rendered HTML. ## [](Phoenix.LiveViewTest.html#live_children/1-examples)Examples ``` {:ok, view, _html} = live(conn, "/thermo") assert [clock_view] = live_children(view) assert render_click(clock_view, :snooze) =~ "snoozing" ``` [](Phoenix.LiveViewTest.html#live_isolated/3) # live\_isolated(conn, live\_view, opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L261)(macro) Spawns a connected LiveView process mounted in isolation as the sole rendered element. Useful for testing LiveViews that are not directly routable, such as those built as small components to be re-used in multiple parents. Testing routable LiveViews is still recommended whenever possible since features such as live navigation require routable LiveViews. ## [](Phoenix.LiveViewTest.html#live_isolated/3-options)Options - `:session` - the session to be given to the LiveView All other options are forwarded to the LiveView for rendering. Refer to [`Phoenix.Component.live_render/3`](Phoenix.Component.html#live_render/3) for a list of supported render options. ## [](Phoenix.LiveViewTest.html#live_isolated/3-examples)Examples ``` {:ok, view, html} = live_isolated(conn, MyAppWeb.ClockLive, session: %{"tz" => "EST"}) ``` Use [`put_connect_params/2`](Phoenix.LiveViewTest.html#put_connect_params/2) to put connect params for a call to [`Phoenix.LiveView.get_connect_params/1`](Phoenix.LiveView.html#get_connect_params/1) in [`Phoenix.LiveView.mount/3`](Phoenix.LiveView.html#c:mount/3): ``` {:ok, view, html} = conn |> put_connect_params(%{"param" => "value"}) |> live_isolated(AppWeb.ClockLive, session: %{"tz" => "EST"}) ``` [](Phoenix.LiveViewTest.html#live_redirect/2) # live\_redirect(view, opts) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1766) Performs a live redirect from one LiveView to another. When redirecting between two LiveViews of the same `live_session`, mounts the new LiveView and shutsdown the previous one, which mimics general browser live navigation behaviour. When attempting to navigate from a LiveView of a different `live_session`, an error redirect condition is returned indicating a failed `push_navigate` from the client. ## [](Phoenix.LiveViewTest.html#live_redirect/2-examples)Examples ``` assert {:ok, page_live, _html} = live(conn, "/page/1") assert {:ok, page2_live, _html} = live(conn, "/page/2") assert {:error, {:redirect, _}} = live_redirect(page2_live, to: "/admin") ``` [](Phoenix.LiveViewTest.html#open_browser/2) # open\_browser(view\_or\_element, open\_fun \\\\ &amp;open\_with\_system\_cmd/1) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1499) Open the default browser to display current HTML of `view_or_element`. ## [](Phoenix.LiveViewTest.html#open_browser/2-examples)Examples ``` view |> element("#term > :first-child", "Increment") |> open_browser() assert view |> form("#term", user: %{name: "hello"}) |> open_browser() |> render_submit() =~ "Name updated" ``` [](Phoenix.LiveViewTest.html#page_title/1) # page\_title(view) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1272) Returns the most recent title that was updated via a `page_title` assign. ## [](Phoenix.LiveViewTest.html#page_title/1-examples)Examples ``` render_click(view, :event_that_triggers_page_title_update) assert page_title(view) =~ "my title" ``` [](Phoenix.LiveViewTest.html#preflight_upload/1) # preflight\_upload(upload) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1993) Performs a preflight upload request. Useful for testing external uploaders to retrieve the `:external` entry metadata. ## [](Phoenix.LiveViewTest.html#preflight_upload/1-examples)Examples ``` avatar = file_input(lv, "#my-form-id", :avatar, [%{name: ..., ...}, ...]) assert {:ok, %{ref: _ref, config: %{chunk_size: _}}} = preflight_upload(avatar) ``` [](Phoenix.LiveViewTest.html#put_connect_params/2) # put\_connect\_params(conn, params) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L193) Puts connect params to be used on LiveView connections. See [`Phoenix.LiveView.get_connect_params/1`](Phoenix.LiveView.html#get_connect_params/1). [](Phoenix.LiveViewTest.html#put_submitter/2) # put\_submitter(form, element\_or\_selector) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L596) Puts the submitter `element_or_selector` on the given `form` element. A submitter is an element that initiates the form's submit event on the client. When a submitter is put on an element created with [`form/3`](Phoenix.LiveViewTest.html#form/3) and then the form is submitted via [`render_submit/2`](Phoenix.LiveViewTest.html#render_submit/2), the name/value pair of the submitter will be included in the submit event payload. The given element or selector must exist within the form and match one of the following: - A `button` or `input` element with `type="submit"`. - A `button` element without a `type` attribute. ## [](Phoenix.LiveViewTest.html#put_submitter/2-examples)Examples ``` form = view |> form("#my-form") assert form |> put_submitter("button[name=example]") |> render_submit() =~ "Submitted example" ``` [](Phoenix.LiveViewTest.html#refute_push_event/4) # refute\_push\_event(view, event, payload, timeout \\\\ Application.fetch\_env!(:ex\_unit, :refute\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1626)(macro) Refutes an event will be pushed within timeout. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `refute_receive_timeout` (100 ms). ## [](Phoenix.LiveViewTest.html#refute_push_event/4-examples)Examples ``` refute_push_event view, "scores", %{points: _, user: "josé"} ``` [](Phoenix.LiveViewTest.html#refute_redirected/2) # refute\_redirected(view, to) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1455) Refutes a redirect to a given path was performed. It returns :ok if the specified redirect isn't already in the mailbox. ## [](Phoenix.LiveViewTest.html#refute_redirected/2-examples)Examples ``` render_click(view, :event_that_triggers_redirect_to_path) :ok = refute_redirected view, "/wrong_path" ``` [](Phoenix.LiveViewTest.html#render/1) # render(view\_or\_element) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1059) Returns the HTML string of the rendered view or element. If a view is provided, the entire LiveView is rendered. If a view after calling [`with_target/2`](Phoenix.LiveViewTest.html#with_target/2) or an element are given, only that particular context is returned. ## [](Phoenix.LiveViewTest.html#render/1-examples)Examples ``` {:ok, view, _html} = live(conn, "/thermo") assert render(view) =~ ~s|<button id="alarm">Snooze</div>| assert view |> element("#alarm") |> render() == "Snooze" ``` [](Phoenix.LiveViewTest.html#render_async/2) # render\_async(view\_or\_element, timeout \\\\ Application.fetch\_env!(:ex\_unit, :assert\_receive\_timeout)) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L940) Awaits all current `assign_async` and `start_async` for a given LiveView or element. It renders the LiveView or Element once complete and returns the result. The default `timeout` is [ExUnit](../ex_unit/ExUnit.html#configure/1)'s `assert_receive_timeout` (100 ms). ## [](Phoenix.LiveViewTest.html#render_async/2-examples)Examples ``` {:ok, lv, html} = live(conn, "/path") assert html =~ "loading data..." assert render_async(lv) =~ "data loaded!" ``` [](Phoenix.LiveViewTest.html#render_blur/2) # render\_blur(element, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L824) Sends a blur event given by `element` and returns the rendered result. The `element` is created with [`element/3`](Phoenix.LiveViewTest.html#element/3) and must point to a single element on the page with a `phx-blur` attribute in it. The event name given set on `phx-blur` is then sent to the appropriate LiveView (or component if `phx-target` is set accordingly). All `phx-value-*` entries in the element are sent as values. Extra values can be given with the `value` argument. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_blur/2-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert view |> element("#inactive") |> render_blur() =~ "Tap to wake" ``` [](Phoenix.LiveViewTest.html#render_blur/3) # render\_blur(view, event, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L841) Sends a blur event to the view and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_blur/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_blur(view, :inactive) =~ "Tap to wake" ``` [](Phoenix.LiveViewTest.html#render_change/2) # render\_change(element, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L698) Sends a form change event given by `element` and returns the rendered result. The `element` is created with [`element/3`](Phoenix.LiveViewTest.html#element/3) and must point to a single element on the page with a `phx-change` attribute in it. The event name given set on `phx-change` is then sent to the appropriate LiveView (or component if `phx-target` is set accordingly). All `phx-value-*` entries in the element are sent as values. If you need to pass any extra values or metadata, such as the "\_target" parameter, you can do so by giving a map under the `value` argument. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_change/2-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert view |> element("form") |> render_change(%{deg: 123}) =~ "123 exceeds limits" # Passing metadata {:ok, view, html} = live(conn, "/thermo") assert view |> element("form") |> render_change(%{_target: ["deg"], deg: 123}) =~ "123 exceeds limits" ``` As with [`render_submit/2`](Phoenix.LiveViewTest.html#render_submit/2), hidden input field values can be provided like so: ``` refute view |> form("#term", user: %{name: "hello"}) |> render_change(%{user: %{"hidden_field" => "example"}}) =~ "can't be blank" ``` [](Phoenix.LiveViewTest.html#render_change/3) # render\_change(view, event, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L714) Sends a form change event to the view and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_change/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_change(view, :validate, %{deg: 123}) =~ "123 exceeds limits" ``` [](Phoenix.LiveViewTest.html#render_click/2) # render\_click(element, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L554) Sends a click event given by `element` and returns the rendered result. The `element` is created with [`element/3`](Phoenix.LiveViewTest.html#element/3) and must point to a single element on the page with a `phx-click` attribute in it. The event name given set on `phx-click` is then sent to the appropriate LiveView (or component if `phx-target` is set accordingly). All `phx-value-*` entries in the element are sent as values. Extra values can be given with the `value` argument. If the element does not have a `phx-click` attribute but it is a link (the `<a>` tag), the link will be followed accordingly: - if the link is a `patch`, the current view will be patched - if the link is a `navigate`, this function will return `{:error, {:live_redirect, %{to: url}}}`, which can be followed with [`follow_redirect/2`](Phoenix.LiveViewTest.html#follow_redirect/2) - if the link is a regular link, this function will return `{:error, {:redirect, %{to: url}}}`, which can be followed with [`follow_redirect/2`](Phoenix.LiveViewTest.html#follow_redirect/2) It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_click/2-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert view |> element("button", "Increment") |> render_click() =~ "The temperature is: 30℉" ``` [](Phoenix.LiveViewTest.html#render_click/3) # render\_click(view, event, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L571) Sends a click `event` to the `view` with `value` and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_click/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temperature is: 30℉" assert render_click(view, :inc) =~ "The temperature is: 31℉" ``` [](Phoenix.LiveViewTest.html#render_component/3) # render\_component(component, assigns \\\\ Macro.escape(%{}), opts \\\\ \[]) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L447)(macro) Renders a component. The first argument may either be a function component, as an anonymous function: ``` assert render_component(&Weather.city/1, name: "Kraków") =~ "some markup in component" ``` Or a stateful component as a module. In this case, this function will mount, update, and render the component. The `:id` option is a required argument: ``` assert render_component(MyComponent, id: 123, user: %User{}) =~ "some markup in component" ``` If your component is using the router, you can pass it as argument: ``` assert render_component(MyComponent, %{id: 123, user: %User{}}, router: SomeRouter) =~ "some markup in component" ``` [](Phoenix.LiveViewTest.html#render_focus/2) # render\_focus(element, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L866) Sends a focus event given by `element` and returns the rendered result. The `element` is created with [`element/3`](Phoenix.LiveViewTest.html#element/3) and must point to a single element on the page with a `phx-focus` attribute in it. The event name given set on `phx-focus` is then sent to the appropriate LiveView (or component if `phx-target` is set accordingly). All `phx-value-*` entries in the element are sent as values. Extra values can be given with the `value` argument. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_focus/2-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert view |> element("#inactive") |> render_focus() =~ "Tap to wake" ``` [](Phoenix.LiveViewTest.html#render_focus/3) # render\_focus(view, event, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L883) Sends a focus event to the view and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_focus/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_focus(view, :inactive) =~ "Tap to wake" ``` [](Phoenix.LiveViewTest.html#render_hook/3) # render\_hook(view\_or\_element, event, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L909) Sends a hook event to the view or an element and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_hook/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_hook(view, :refresh, %{deg: 32}) =~ "The temp is: 32℉" ``` If you are pushing events from a hook to a component, then you must pass an `element`, created with [`element/3`](Phoenix.LiveViewTest.html#element/3), as first argument and it must point to a single element on the page with a `phx-target` attribute in it: ``` {:ok, view, _html} = live(conn, "/thermo") assert view |> element("#thermo-component") |> render_hook(:refresh, %{deg: 32}) =~ "The temp is: 32℉" ``` [](Phoenix.LiveViewTest.html#render_keydown/2) # render\_keydown(element, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L738) Sends a keydown event given by `element` and returns the rendered result. The `element` is created with [`element/3`](Phoenix.LiveViewTest.html#element/3) and must point to a single element on the page with a `phx-keydown` or `phx-window-keydown` attribute in it. The event name given set on `phx-keydown` is then sent to the appropriate LiveView (or component if `phx-target` is set accordingly). All `phx-value-*` entries in the element are sent as values. Extra values can be given with the `value` argument. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_keydown/2-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert view |> element("#inc") |> render_keydown() =~ "The temp is: 31℉" ``` [](Phoenix.LiveViewTest.html#render_keydown/3) # render\_keydown(view, event, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L758) Sends a keydown event to the view and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_keydown/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_keydown(view, :inc) =~ "The temp is: 31℉" ``` [](Phoenix.LiveViewTest.html#render_keyup/2) # render\_keyup(element, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L782) Sends a keyup event given by `element` and returns the rendered result. The `element` is created with [`element/3`](Phoenix.LiveViewTest.html#element/3) and must point to a single element on the page with a `phx-keyup` or `phx-window-keyup` attribute in it. The event name given set on `phx-keyup` is then sent to the appropriate LiveView (or component if `phx-target` is set accordingly). All `phx-value-*` entries in the element are sent as values. Extra values can be given with the `value` argument. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_keyup/2-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert view |> element("#inc") |> render_keyup() =~ "The temp is: 31℉" ``` [](Phoenix.LiveViewTest.html#render_keyup/3) # render\_keyup(view, event, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L799) Sends a keyup event to the view and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_keyup/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_keyup(view, :inc) =~ "The temp is: 31℉" ``` [](Phoenix.LiveViewTest.html#render_patch/2) # render\_patch(view, path) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L979) Simulates a `push_patch` to the given `path` and returns the rendered result. [](Phoenix.LiveViewTest.html#render_submit/2) # render\_submit(element, value \\\\ %{}) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L641) Sends a form submit event given by `element` and returns the rendered result. The `element` is created with [`element/3`](Phoenix.LiveViewTest.html#element/3) and must point to a single element on the page with a `phx-submit` attribute in it. The event name given set on `phx-submit` is then sent to the appropriate LiveView (or component if `phx-target` is set accordingly). All `phx-value-*` entries in the element are sent as values. Extra values, including hidden input fields, can be given with the `value` argument. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_submit/2-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert view |> element("form") |> render_submit(%{deg: 123, avatar: upload}) =~ "123 exceeds limits" ``` To submit a form along with some with hidden input values: ``` assert view |> form("#term", user: %{name: "hello"}) |> render_submit(%{user: %{"hidden_field" => "example"}}) =~ "Name updated" ``` To submit a form by a specific submit element via [`put_submitter/2`](Phoenix.LiveViewTest.html#put_submitter/2): ``` assert view |> form("#term", user: %{name: "hello"}) |> put_submitter("button[name=example_action]") |> render_submit() =~ "Action taken" ``` [](Phoenix.LiveViewTest.html#render_submit/3) # render\_submit(view, event, value) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L657) Sends a form submit event to the view and returns the rendered result. It returns the contents of the whole LiveView or an `{:error, redirect}` tuple. ## [](Phoenix.LiveViewTest.html#render_submit/3-examples)Examples ``` {:ok, view, html} = live(conn, "/thermo") assert html =~ "The temp is: 30℉" assert render_submit(view, :refresh, %{deg: 32}) =~ "The temp is: 32℉" ``` [](Phoenix.LiveViewTest.html#render_upload/3) # render\_upload(upload, entry\_name, percent \\\\ 100) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1946) Performs an upload of a file input and renders the result. See [`file_input/4`](Phoenix.LiveViewTest.html#file_input/4) for details on building a file input. ## [](Phoenix.LiveViewTest.html#render_upload/3-examples)Examples Given the following LiveView template: ``` <%= for entry <- @uploads.avatar.entries do %> {entry.name}: {entry.progress}% <% end %> ``` Your test case can assert the uploaded content: ``` avatar = file_input(lv, "#my-form-id", :avatar, [ %{ last_modified: 1_594_171_879_000, name: "myfile.jpeg", content: File.read!("myfile.jpg"), size: 1_396_009, type: "image/jpeg" } ]) assert render_upload(avatar, "myfile.jpeg") =~ "100%" ``` By default, the entire file is chunked to the server, but an optional percentage to chunk can be passed to test chunk-by-chunk uploads: ``` assert render_upload(avatar, "myfile.jpeg", 49) =~ "49%" assert render_upload(avatar, "myfile.jpeg", 51) =~ "100%" ``` Before making assertions about the how the upload is consumed server-side, you will need to call [`render_submit/1`](Phoenix.LiveViewTest.html#render_submit/1). In the case where an upload progress callback issues a navigate, patch, or redirect, the following will be returned: - for a patch, the current view will be patched - for a navigate, this function will return `{:error, {:live_redirect, %{to: url}}}`, which can be followed with [`follow_redirect/2`](Phoenix.LiveViewTest.html#follow_redirect/2) - for a regular redirect, this function will return `{:error, {:redirect, %{to: url}}}`, which can be followed with [`follow_redirect/2`](Phoenix.LiveViewTest.html#follow_redirect/2) [](Phoenix.LiveViewTest.html#rendered_to_string/1) # rendered\_to\_string(rendered) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L516) Converts a rendered template to a string. ## [](Phoenix.LiveViewTest.html#rendered_to_string/1-examples)Examples ``` import Phoenix.Component import Phoenix.LiveViewTest test "greets" do assigns = %{} assert rendered_to_string(~H""" <MyComponents.greet name="Mary" /> """) == "<div>Hello, Mary!</div>" end ``` [](Phoenix.LiveViewTest.html#submit_form/2) # submit\_form(form, conn) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1860)(macro) Receives a form element and submits the HTTP request through the plug pipeline. Imagine you have a LiveView that validates form data, but submits the form to a controller via the normal form `action` attribute. This is especially useful in scenarios where the result of a form submit needs to write to the plug session. You can follow submit the form with the `%Plug.Conn{}`, like this: ``` form = form(live_view, selector, %{"form" => "data"}) # Now submit the LiveView form to the plug pipeline conn = submit_form(form, conn) assert conn.method == "POST" assert conn.params == %{"form" => "data"} ``` [](Phoenix.LiveViewTest.html#with_target/2) # with\_target(view, target) [](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/lib/phoenix_live_view/test/live_view_test.ex#L1078) Sets the target of the view for events. This emulates `phx-target` directly in tests, without having to dispatch the event to a specific element. This can be useful for invoking events to one or multiple components at the same time: ``` view |> with_target("#user-1,#user-2") |> render_click("Hide", %{}) ``` [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/api-reference.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view "View Source") API Reference Phoenix LiveView v1.0.2 ## [](api-reference.html#modules)Modules [Phoenix.Component](Phoenix.Component.html) Define reusable function components with HEEx templates. [Phoenix.LiveComponent](Phoenix.LiveComponent.html) LiveComponents are a mechanism to compartmentalize state, markup, and events in LiveView. [Phoenix.LiveComponent.CID](Phoenix.LiveComponent.CID.html) The struct representing an internal unique reference to the component instance, available as the `@myself` assign in live components. [Phoenix.LiveView](Phoenix.LiveView.html) A LiveView is a process that receives events, updates its state, and renders updates to a page as diffs. [Phoenix.LiveView.AsyncResult](Phoenix.LiveView.AsyncResult.html) Provides a data structure for tracking the state of an async assign. [Phoenix.LiveView.Component](Phoenix.LiveView.Component.html) The struct returned by components in .heex templates. [Phoenix.LiveView.Comprehension](Phoenix.LiveView.Comprehension.html) The struct returned by for-comprehensions in .heex templates. [Phoenix.LiveView.Controller](Phoenix.LiveView.Controller.html) Helpers for rendering LiveViews from a controller. [Phoenix.LiveView.Engine](Phoenix.LiveView.Engine.html) An [`EEx`](../eex/EEx.html) template engine that tracks changes. [Phoenix.LiveView.HTMLEngine](Phoenix.LiveView.HTMLEngine.html) The HTMLEngine that powers `.heex` templates and the `~H` sigil. [Phoenix.LiveView.HTMLFormatter](Phoenix.LiveView.HTMLFormatter.html) Format HEEx templates from `.heex` files or `~H` sigils. [Phoenix.LiveView.JS](Phoenix.LiveView.JS.html) Provides commands for executing JavaScript utility operations on the client. [Phoenix.LiveView.Logger](Phoenix.LiveView.Logger.html) Instrumenter to handle logging of [`Phoenix.LiveView`](Phoenix.LiveView.html) and [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) life-cycle events. [Phoenix.LiveView.ReloadError](Phoenix.LiveView.ReloadError.html) [Phoenix.LiveView.Rendered](Phoenix.LiveView.Rendered.html) The struct returned by .heex templates. [Phoenix.LiveView.Router](Phoenix.LiveView.Router.html) Provides LiveView routing for Phoenix routers. [Phoenix.LiveView.Socket](Phoenix.LiveView.Socket.html) The LiveView socket for Phoenix Endpoints. [Phoenix.LiveView.TagEngine](Phoenix.LiveView.TagEngine.html) An EEx engine that understands tags. [Phoenix.LiveView.UploadConfig](Phoenix.LiveView.UploadConfig.html) The struct representing an upload. [Phoenix.LiveView.UploadEntry](Phoenix.LiveView.UploadEntry.html) The struct representing an upload entry. [Phoenix.LiveView.UploadWriter](Phoenix.LiveView.UploadWriter.html) Provide a behavior for writing uploaded chunks to a final destination. [Phoenix.LiveViewTest](Phoenix.LiveViewTest.html) Conveniences for testing function components as well as LiveViews and LiveComponents. [Phoenix.LiveViewTest.Element](Phoenix.LiveViewTest.Element.html) The struct returned by [`Phoenix.LiveViewTest.element/3`](Phoenix.LiveViewTest.html#element/3). [Phoenix.LiveViewTest.Upload](Phoenix.LiveViewTest.Upload.html) The struct returned by [`Phoenix.LiveViewTest.file_input/4`](Phoenix.LiveViewTest.html#file_input/4). [Phoenix.LiveViewTest.View](Phoenix.LiveViewTest.View.html) The struct for testing LiveViews. [Next Page → Changelog](changelog.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/assigns-eex.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/assigns-eex.md#L1 "View Source") Assigns and HEEx templates All of the data in a LiveView is stored in the socket, which is a server side struct called [`Phoenix.LiveView.Socket`](Phoenix.LiveView.Socket.html). Your own data is stored under the `assigns` key of said struct. The server data is never shared with the client beyond what your template renders. Phoenix template language is called HEEx (HTML+EEx). EEx is Embedded Elixir, an Elixir string template engine. Those templates are either files with the `.heex` extension or they are created directly in source files via the `~H` sigil. You can learn more about the HEEx syntax by checking the docs for [the `~H` sigil](Phoenix.Component.html#sigil_H/2). The [`Phoenix.Component.assign/2`](Phoenix.Component.html#assign/2) and [`Phoenix.Component.assign/3`](Phoenix.Component.html#assign/3) functions help store those values. Those values can be accessed in the LiveView as `socket.assigns.name` but they are accessed inside HEEx templates as `@name`. In this section, we are going to cover how LiveView minimizes the payload over the wire by understanding the interplay between assigns and templates. ## [](assigns-eex.html#change-tracking)Change tracking When you first render a `.heex` template, it will send all of the static and dynamic parts of the template to the client. Imagine the following template: ``` <h1>{expand_title(@title)}</h1> ``` It has two static parts, `<h1>` and `</h1>` and one dynamic part made of `expand_title(@title)`. Further rendering of this template won't resend the static parts and it will only resend the dynamic part if it changes. The tracking of changes is done via assigns. If the `@title` assign changes, then LiveView will execute the dynamic parts of the template, `expand_title(@title)`, and send the new content. If `@title` is the same, nothing is executed and nothing is sent. Change tracking also works when accessing map/struct fields. Take this template: ``` <div id={"user_#{@user.id}"}> {@user.name} </div> ``` If the `@user.name` changes but `@user.id` doesn't, then LiveView will re-render only `@user.name` and it will not execute or resend `@user.id` at all. The change tracking also works when rendering other templates as long as they are also `.heex` templates: ``` {render("child_template.html", assigns)} ``` Or when using function components: ``` <.show_name name={@user.name} /> ``` The assign tracking feature also implies that you MUST avoid performing direct operations in the template. For example, if you perform a database query in your template: ``` <%= for user <- Repo.all(User) do %> {user.name} <% end %> ``` Then Phoenix will never re-render the section above, even if the number of users in the database changes. Instead, you need to store the users as assigns in your LiveView before it renders the template: ``` assign(socket, :users, Repo.all(User)) ``` Generally speaking, **data loading should never happen inside the template**, regardless if you are using LiveView or not. The difference is that LiveView enforces this best practice. ## [](assigns-eex.html#pitfalls)Pitfalls There are some common pitfalls to keep in mind when using the `~H` sigil or `.heex` templates inside LiveViews. ### [](assigns-eex.html#variables)Variables Due to the scope of variables, LiveView has to disable change tracking whenever variables are used in the template, with the exception of variables introduced by Elixir block constructs such as `case`, `for`, `if`, and others. Therefore, you **must avoid** code like this in your HEEx templates: ``` <% some_var = @x + @y %> {some_var} ``` Instead, use a function: ``` {sum(@x, @y)} ``` Similarly, **do not** define variables at the top of your `render` function for LiveViews or LiveComponents. Since LiveView cannot track `sum` or `title`, if either value changes, both must be re-rendered by LiveView. ``` def render(assigns) do sum = assigns.x + assigns.y title = assigns.title ~H""" <h1>{title}</h1> {sum} """ end ``` Instead use the `assign/2`, `assign/3`, `assign_new/3`, and `update/3` functions to compute it. Any assign defined or updated this way will be marked as changed, while other assigns like `@title` will still be tracked by LiveView. ``` assign(assigns, sum: assigns.x + assigns.y) ``` The same functions can be used inside function components too: ``` attr :x, :integer, required: true attr :y, :integer, required: true attr :title, :string, required: true def sum_component(assigns) do assigns = assign(assigns, sum: assigns.x + assigns.y) ~H""" <h1>{@title}</h1> {@sum} """ end ``` Generally speaking, avoid accessing variables inside `HEEx` templates, as code that access variables is always executed on every render. The exception are variables introduced by Elixir's block constructs. For example, accessing the `post` variable defined by the comprehension below works as expected: ``` <%= for post <- @posts do %> ... <% end %> ``` ### [](assigns-eex.html#the-assigns-variable)The `assigns` variable When talking about variables, it is also worth discussing the `assigns` special variable. Every time you use the `~H` sigil, you must define an `assigns` variable, which is also available on every `.heex` template. However, we must avoid accessing this variable directly inside templates and instead use `@` for accessing specific keys. This also applies to function components. Let's see some examples. Sometimes you might want to pass all assigns from one function component to another. For example, imagine you have a complex `card` component with header, content and footer section. You might refactor your component into three smaller components internally: ``` def card(assigns) do ~H""" <div class="card"> <.card_header {assigns} /> <.card_body {assigns} /> <.card_footer {assigns} /> </div> """ end defp card_header(assigns) do ... end defp card_body(assigns) do ... end defp card_footer(assigns) do ... end ``` Because of the way function components handle attributes, the above code will not perform change tracking and it will always re-render all three components on every change. Generally, you should avoid passing all assigns and instead be explicit about which assigns the child components need: ``` def card(assigns) do ~H""" <div class="card"> <.card_header title={@title} class={@title_class} /> <.card_body> {render_slot(@inner_block)} </.card_body> <.card_footer on_close={@on_close} /> </div> """ end ``` If you really need to pass all assigns you should instead use the regular function call syntax. This is the only case where accessing `assigns` inside templates is acceptable: ``` def card(assigns) do ~H""" <div class="card"> {card_header(assigns)} {card_body(assigns)} {card_footer(assigns)} </div> """ end ``` This ensures that the change tracking information from the parent component is passed to each child component, only re-rendering what is necessary. However, generally speaking, it is best to avoid passing `assigns` altogether and instead let LiveView figure out the best way to track changes. ### [](assigns-eex.html#summary)Summary To sum up: 1. Avoid defining local variables inside HEEx templates, except within Elixir's constructs 2. Avoid passing or accessing the `assigns` variable inside HEEx templates [← Previous Page Welcome](welcome.html) [Next Page → Deployments and recovery](deployments.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/assigns-eex.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/bindings.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/client/bindings.md#L1 "View Source") Bindings Phoenix supports DOM element bindings for client-server interaction. For example, to react to a click on a button, you would render the element: ``` <button phx-click="inc_temperature">+</button> ``` Then on the server, all LiveView bindings are handled with the `handle_event` callback, for example: ``` def handle_event("inc_temperature", _value, socket) do {:ok, new_temp} = Thermostat.inc_temperature(socket.assigns.id) {:noreply, assign(socket, :temperature, new_temp)} end ``` BindingAttributes[Params](bindings.html#click-events)`phx-value-*`[Click Events](bindings.html#click-events)`phx-click`, `phx-click-away`[Form Events](form-bindings.html)`phx-change`, `phx-submit`, `phx-disable-with`, `phx-trigger-action`, `phx-auto-recover`[Focus Events](bindings.html#focus-and-blur-events)`phx-blur`, `phx-focus`, `phx-window-blur`, `phx-window-focus`[Key Events](bindings.html#key-events)`phx-keydown`, `phx-keyup`, `phx-window-keydown`, `phx-window-keyup`, `phx-key`[Scroll Events](bindings.html#scroll-events-and-infinite-pagination)`phx-viewport-top`, `phx-viewport-bottom`[DOM Patching](bindings.html#dom-patching)`phx-update`, `phx-mounted`, `phx-remove`[JS Interop](js-interop.html#client-hooks-via-phx-hook)`phx-hook`[Lifecycle Events](bindings.html#lifecycle-events)`phx-connected`, `phx-disconnected`[Rate Limiting](bindings.html#rate-limiting-events-with-debounce-and-throttle)`phx-debounce`, `phx-throttle`[Static tracking](Phoenix.LiveView.html#static_changed?/1)`phx-track-static` ## [](bindings.html#click-events)Click Events The `phx-click` binding is used to send click events to the server. When any client event, such as a `phx-click` click is pushed, the value sent to the server will be chosen with the following priority: - The `:value` specified in [`Phoenix.LiveView.JS.push/3`](Phoenix.LiveView.JS.html#push/3), such as: ``` <div phx-click={JS.push("inc", value: %{myvar1: @val1})}> ``` - Any number of optional `phx-value-` prefixed attributes, such as: ``` <div phx-click="inc" phx-value-myvar1="val1" phx-value-myvar2="val2"> ``` will send the following map of params to the server: ``` def handle_event("inc", %{"myvar1" => "val1", "myvar2" => "val2"}, socket) do ``` If the `phx-value-` prefix is used, the server payload will also contain a `"value"` if the element's value attribute exists. - The payload will also include any additional user defined metadata of the client event. For example, the following `LiveSocket` client option would send the coordinates and `altKey` information for all clicks: ``` let liveSocket = new LiveSocket("/live", Socket, { params: {_csrf_token: csrfToken}, metadata: { click: (e, el) => { return { altKey: e.altKey, clientX: e.clientX, clientY: e.clientY } } } }) ``` The `phx-click-away` event is fired when a click event happens outside of the element. This is useful for hiding toggled containers like drop-downs. ## [](bindings.html#focus-and-blur-events)Focus and Blur Events Focus and blur events may be bound to DOM elements that emit such events, using the `phx-blur`, and `phx-focus` bindings, for example: ``` <input name="email" phx-focus="myfocus" phx-blur="myblur"/> ``` To detect when the page itself has received focus or blur, `phx-window-focus` and `phx-window-blur` may be specified. These window level events may also be necessary if the element in consideration (most often a `div` with no tabindex) cannot receive focus. Like other bindings, `phx-value-*` can be provided on the bound element, and those values will be sent as part of the payload. For example: ``` <div class="container" phx-window-focus="page-active" phx-window-blur="page-inactive" phx-value-page="123"> ... </div> ``` ## [](bindings.html#key-events)Key Events The `onkeydown`, and `onkeyup` events are supported via the `phx-keydown`, and `phx-keyup` bindings. Each binding supports a `phx-key` attribute, which triggers the event for the specific key press. If no `phx-key` is provided, the event is triggered for any key press. When pushed, the value sent to the server will contain the `"key"` that was pressed, plus any user-defined metadata. For example, pressing the Escape key looks like this: ``` %{"key" => "Escape"} ``` To capture additional user-defined metadata, the `metadata` option for keydown events may be provided to the `LiveSocket` constructor. For example: ``` let liveSocket = new LiveSocket("/live", Socket, { params: {_csrf_token: csrfToken}, metadata: { keydown: (e, el) => { return { key: e.key, metaKey: e.metaKey, repeat: e.repeat } } } }) ``` To determine which key has been pressed you should use `key` value. The available options can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values) or via the [Key Event Viewer](https://w3c.github.io/uievents/tools/key-event-viewer.html). *Note*: `phx-keyup` and `phx-keydown` are not supported on inputs. Instead use form bindings, such as `phx-change`, `phx-submit`, etc. *Note*: it is possible for certain browser features like autofill to trigger key events with no `"key"` field present in the value map sent to the server. For this reason, we recommend always having a fallback catch-all event handler for LiveView key bindings. By default, the bound element will be the event listener, but a window-level binding may be provided via `phx-window-keydown` or `phx-window-keyup`, for example: ``` def render(assigns) do ~H""" <div id="thermostat" phx-window-keyup="update_temp"> Current temperature: {@temperature} </div> """ end def handle_event("update_temp", %{"key" => "ArrowUp"}, socket) do {:ok, new_temp} = Thermostat.inc_temperature(socket.assigns.id) {:noreply, assign(socket, :temperature, new_temp)} end def handle_event("update_temp", %{"key" => "ArrowDown"}, socket) do {:ok, new_temp} = Thermostat.dec_temperature(socket.assigns.id) {:noreply, assign(socket, :temperature, new_temp)} end def handle_event("update_temp", _, socket) do {:noreply, socket} end ``` ## [](bindings.html#rate-limiting-events-with-debounce-and-throttle)Rate limiting events with Debounce and Throttle All events can be rate-limited on the client by using the `phx-debounce` and `phx-throttle` bindings, with the exception of the `phx-blur` binding, which is fired immediately. Rate limited and debounced events have the following behavior: - `phx-debounce` - Accepts either an integer timeout value (in milliseconds), or `"blur"`. When an integer is provided, emitting the event is delayed by the specified milliseconds. When `"blur"` is provided, emitting the event is delayed until the field is blurred by the user. When the value is omitted a default of 300ms is used. Debouncing is typically used for input elements. - `phx-throttle` - Accepts an integer timeout value to throttle the event in milliseconds. Unlike debounce, throttle will immediately emit the event, then rate limit it at once per provided timeout. When the value is omitted a default of 300ms is used. Throttling is typically used to rate limit clicks, mouse and keyboard actions. For example, to avoid validating an email until the field is blurred, while validating the username at most every 2 seconds after a user changes the field: ``` <form phx-change="validate" phx-submit="save"> <input type="text" name="user[email]" phx-debounce="blur"/> <input type="text" name="user[username]" phx-debounce="2000"/> </form> ``` And to rate limit a volume up click to once every second: ``` <button phx-click="volume_up" phx-throttle="1000">+</button> ``` Likewise, you may throttle held-down keydown: ``` <div phx-window-keydown="keydown" phx-throttle="500"> ... </div> ``` Unless held-down keys are required, a better approach is generally to use `phx-keyup` bindings which only trigger on key up, thereby being self-limiting. However, `phx-keydown` is useful for games and other use cases where a constant press on a key is desired. In such cases, throttle should always be used. ### [](bindings.html#debounce-and-throttle-special-behavior)Debounce and Throttle special behavior The following specialized behavior is performed for forms and keydown bindings: - When a `phx-submit`, or a `phx-change` for a different input is triggered, any current debounce or throttle timers are reset for existing inputs. - A `phx-keydown` binding is only throttled for key repeats. Unique keypresses back-to-back will dispatch the pressed key events. ## [](bindings.html#js-commands)JS commands LiveView bindings support a JavaScript command interface via the [`Phoenix.LiveView.JS`](Phoenix.LiveView.JS.html) module, which allows you to specify utility operations that execute on the client when firing `phx-` binding events, such as `phx-click`, `phx-change`, etc. Commands compose together to allow you to push events, add classes to elements, transition elements in and out, and more. See the [`Phoenix.LiveView.JS`](Phoenix.LiveView.JS.html) documentation for full usage. For a small example of what's possible, imagine you want to show and hide a modal on the page without needing to make the round trip to the server to render the content: ``` <div id="modal" class="modal"> My Modal </div> <button phx-click={JS.show(to: "#modal", transition: "fade-in")}> show modal </button> <button phx-click={JS.hide(to: "#modal", transition: "fade-out")}> hide modal </button> <button phx-click={JS.toggle(to: "#modal", in: "fade-in", out: "fade-out")}> toggle modal </button> ``` Or if your UI library relies on classes to perform the showing or hiding: ``` <div id="modal" class="modal"> My Modal </div> <button phx-click={JS.add_class("show", to: "#modal", transition: "fade-in")}> show modal </button> <button phx-click={JS.remove_class("show", to: "#modal", transition: "fade-out")}> hide modal </button> ``` Commands compose together. For example, you can push an event to the server and immediately hide the modal on the client: ``` <div id="modal" class="modal"> My Modal </div> <button phx-click={JS.push("modal-closed") |> JS.remove_class("show", to: "#modal", transition: "fade-out")}> hide modal </button> ``` It is also useful to extract commands into their own functions: ``` alias Phoenix.LiveView.JS def hide_modal(js \\ %JS{}, selector) do js |> JS.push("modal-closed") |> JS.remove_class("show", to: selector, transition: "fade-out") end ``` ``` <button phx-click={hide_modal("#modal")}>hide modal</button> ``` The [`Phoenix.LiveView.JS.push/3`](Phoenix.LiveView.JS.html#push/3) command is particularly powerful in allowing you to customize the event being pushed to the server. For example, imagine you start with a familiar `phx-click` which pushes a message to the server when clicked: ``` <button phx-click="clicked">click</button> ``` Now imagine you want to customize what happens when the `"clicked"` event is pushed, such as which component should be targeted, which element should receive CSS loading state classes, etc. This can be accomplished with options on the JS push command. For example: ``` <button phx-click={JS.push("clicked", target: @myself, loading: ".container")}>click</button> ``` See [`Phoenix.LiveView.JS.push/3`](Phoenix.LiveView.JS.html#push/3) for all supported options. ## [](bindings.html#dom-patching)DOM patching A container can be marked with `phx-update` to configure how the DOM is updated. The following values are supported: - `replace` - the default operation. Replaces the element with the contents - `stream` - supports stream operations. Streams are used to manage large collections in the UI without having to store the collection on the server - `ignore` - ignores updates to the DOM regardless of new content changes. This is useful for client-side interop with existing libraries that do their own DOM operations When using `phx-update`, a unique DOM ID must always be set in the container. If using "stream", a DOM ID must also be set for each child. When inserting stream elements containing an ID already present in the container, LiveView will replace the existing element with the new content. See [`Phoenix.LiveView.stream/3`](Phoenix.LiveView.html#stream/3) for more information. The "ignore" behaviour is frequently used when you need to integrate with another JS library. Updates from the server to the element's content and attributes are ignored, *except for data attributes*. Changes, additions, and removals from the server to data attributes are merged with the ignored element which can be used to pass data to the JS handler. To react to elements being mounted to the DOM, the `phx-mounted` binding can be used. For example, to animate an element on mount: ``` <div phx-mounted={JS.transition("animate-ping", time: 500)}> ``` If `phx-mounted` is used on the initial page render, it will be invoked only after the initial WebSocket connection is established. To react to elements being removed from the DOM, the `phx-remove` binding may be specified, which can contain a [`Phoenix.LiveView.JS`](Phoenix.LiveView.JS.html) command to execute. The `phx-remove` command is only executed for the removed parent element. It does not cascade to children. To react to elements being updated in the DOM, you'll need to use a [hook](js-interop.html#client-hooks-via-phx-hook), which gives you full access to the element life-cycle. ## [](bindings.html#lifecycle-events)Lifecycle events LiveView supports the `phx-connected` and `phx-disconnected` bindings to react to connection lifecycle events with JS commands. For example, to show an element when the LiveView has lost its connection and hide it when the connection recovers: ``` <div id="status" class="hidden" phx-disconnected={JS.show()} phx-connected={JS.hide()}> Attempting to reconnect... </div> ``` `phx-connected` and `phx-disconnected` are only executed when operating inside a LiveView container. For static templates, they will have no effect. ## [](bindings.html#liveview-events-prefix)LiveView events prefix The `lv:` event prefix supports LiveView specific features that are handled by LiveView without calling the user's `handle_event/3` callbacks. Today, the following events are supported: - `lv:clear-flash` – clears the flash when sent to the server. If a `phx-value-key` is provided, the specific key will be removed from the flash. For example: ``` <p class="alert" phx-click="lv:clear-flash" phx-value-key="info"> {Phoenix.Flash.get(@flash, :info)} </p> ``` ## [](bindings.html#scroll-events-and-infinite-pagination)Scroll events and infinite pagination The `phx-viewport-top` and `phx-viewport-bottom` bindings allow you to detect when a container's first child reaches the top of the viewport, or the last child reaches the bottom of the viewport. This is useful for infinite scrolling where you want to send paging events for the next results set or previous results set as the user is scrolling up and down and reaches the top or bottom of the viewport. Generally, applications will add padding above and below a container when performing infinite scrolling to allow smooth scrolling as results are loaded. Combined with [`Phoenix.LiveView.stream/3`](Phoenix.LiveView.html#stream/3), the `phx-viewport-top` and `phx-viewport-bottom` allow for infinite virtualized list that only keeps a small set of actual elements in the DOM. For example: ``` def mount(_, _, socket) do {:ok, socket |> assign(page: 1, per_page: 20) |> paginate_posts(1)} end defp paginate_posts(socket, new_page) when new_page >= 1 do %{per_page: per_page, page: cur_page} = socket.assigns posts = Blog.list_posts(offset: (new_page - 1) * per_page, limit: per_page) {posts, at, limit} = if new_page >= cur_page do {posts, -1, per_page * 3 * -1} else {Enum.reverse(posts), 0, per_page * 3} end case posts do [] -> assign(socket, end_of_timeline?: at == -1) [_ | _] = posts -> socket |> assign(end_of_timeline?: false) |> assign(:page, new_page) |> stream(:posts, posts, at: at, limit: limit) end end ``` Our `paginate_posts` function fetches a page of posts, and determines if the user is paging to a previous page or next page. Based on the direction of paging, the stream is either prepended to, or appended to with `at` of `0` or `-1` respectively. We also set the `limit` of the stream to three times the `per_page` to allow enough posts in the UI to appear as an infinite list, but small enough to maintain UI performance. We also set an `@end_of_timeline?` assign to track whether the user is at the end of results or not. Finally, we update the `@page` assign and posts stream. We can then wire up our container to support the viewport events: ``` <ul id="posts" phx-update="stream" phx-viewport-top={@page > 1 && "prev-page"} phx-viewport-bottom={!@end_of_timeline? && "next-page"} phx-page-loading class={[ if(@end_of_timeline?, do: "pb-10", else: "pb-[calc(200vh)]"), if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]") ]} > <li :for={{id, post} <- @streams.posts} id={id}> <.post_card post={post} /> </li> </ul> <div :if={@end_of_timeline?} class="mt-5 text-[50px] text-center"> 🎉 You made it to the beginning of time 🎉 </div> ``` There's not much here, but that's the point! This little snippet of UI is driving a fully virtualized list with bidirectional infinite scrolling. We use the `phx-viewport-top` binding to send the `"prev-page"` event to the LiveView, but only if the user is beyond the first page. It doesn't make sense to load negative page results, so we remove the binding entirely in those cases. Next, we wire up `phx-viewport-bottom` to send the `"next-page"` event, but only if we've yet to reach the end of the timeline. Finally, we conditionally apply some CSS classes which sets a large top and bottom padding to twice the viewport height based on the current pagination for smooth scrolling. To complete our solution, we only need to handle the `"prev-page"` and `"next-page"` events in the LiveView: ``` def handle_event("next-page", _, socket) do {:noreply, paginate_posts(socket, socket.assigns.page + 1)} end def handle_event("prev-page", %{"_overran" => true}, socket) do {:noreply, paginate_posts(socket, 1)} end def handle_event("prev-page", _, socket) do if socket.assigns.page > 1 do {:noreply, paginate_posts(socket, socket.assigns.page - 1)} else {:noreply, socket} end end ``` This code simply calls the `paginate_posts` function we defined as our first step, using the current or next page to drive the results. Notice that we match on a special `"_overran" => true` parameter in our `"prev-page"` event. The viewport events send this parameter when the user has "overran" the viewport top or bottom. Imagine the case where the user is scrolling back up through many pages of results, but grabs the scrollbar and returns immediately to the top of the page. This means our `<ul id="posts">` container was overrun by the top of the viewport, and we need to reset the the UI to page the first page. [← Previous Page Uploads](uploads.html) [Next Page → External uploads](external-uploads.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/client/bindings.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/changelog.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/CHANGELOG.md#L1 "View Source") Changelog ## [](changelog.html#backwards-incompatible-changes-for-1-0)Backwards incompatible changes for 1.0 LiveView 1.0 removes the client-based `phx-feedback-for` annotation for showing and hiding input feedback, such as validation errors. This has been replaced by `Phoenix.Component.used_input?/2`, which handles showing and hiding feedback using standard server rendering. A backwards-compatible shim can be used to maintain `phx-feedback-for` in your existing applications: 1. Save the [`phx_feedback_dom.js` shim](https://gist.github.com/chrismccord/c4c60328c6ac5ec29e167bb115315d82) to your local `assets/js/phx_feedback_dom.js`. 2. Import it into your `assets/js/app.js`. 3. Add a new `dom` option to your `LiveSocket` constructor, or wrap the existing value: ``` import {Socket} from "phoenix"; import {LiveSocket} from "phoenix_live_view" import phxFeedbackDom from "./phx_feedback_dom" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, { params: {_csrf_token: csrfToken}, dom: phxFeedbackDom({}) }) ``` Additionally, the `phx-page-loading` attribute has been removed in favor of using the `page_loading: true` option to [`Phoenix.LiveView.JS.push/2`](Phoenix.LiveView.JS.html#push/2) as needed. ### [](changelog.html#migrating-from-phx-feedback-for)Migrating from phx-feedback-for While we provide [a shim for existing applications](https://gist.github.com/chrismccord/c4c60328c6ac5ec29e167bb115315d82) relying on phx-feedback-for, you might want to migrate to the new approach. The following guides you through the necessary changes assuming a project generated with a recent (Phoenix 1.7), but pre LiveView 1.0 version of the phx generators. For more general use cases, please also have a look at the documentation for [`used_input?/1`](1.0.0/Phoenix.Component.html#used_input?/1). First, ensure that you are using the latest versions of `:phoenix_ecto` and `:phoenix_html`. At the time of writing: ``` {:phoenix_ecto, "~> 4.5"}, {:phoenix_html, "~> 4.1"}, ``` #### Core components 1. Adjust the core components to omit the `phx-feedback-for` attribute and the `phx-no-feedback` classes. This shows one example for the textarea input, but there are more cases that need to be adjusted accordingly: ``` def input(%{type: "textarea"} = assigns) do ~H""" - <div phx-feedback-for={@name}> + <div> <.label for={@id}><%= @label %></.label> <textarea id={@id} name={@name} class={[ - "mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6", - "min-h-[6rem] phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400", + "mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem]", @errors == [] && "border-zinc-300 focus:border-zinc-400", @errors != [] && "border-rose-400 focus:border-rose-400" ]} ``` The following regex could be used to find and replace the relevant `phx-no-feedback` classes: `/phx-no-feedback:[\w\-\d:]+/`. 2. Filter the errors in the initial function for [`Phoenix.HTML.FormField`](../phoenix_html/4.1.1/Phoenix.HTML.FormField.html)s: ``` def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do + errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] assigns |> assign(field: nil, id: assigns.id || field.id) - |> assign(:errors, Enum.map(field.errors, &translate_error(&1))) + |> assign(:errors, Enum.map(errors, &translate_error(&1))) |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end) |> assign_new(:value, fn -> field.value end) |> input() end ``` 3. You can remove the phx-no-feedback tailwind variant helper from your `tailwind.config.js`: ``` // - plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])), plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), ``` #### phx.gen.auth The settings page generated by phx.gen.auth creates a form to change the user's email as well as the password. Because of the way the current password is checked in previous variants of the code, the error message for an invalid password is not visible when migrating to `used_input?`. To make this work, two changes need to be made to the generated user module: 1. add a new virtual field `:current_password` to the schema: ``` field :hashed_password, :string, redact: true + field :current_password, :string, virtual: true, redact: true ``` 2. cast the `current_password` field in the `validate_current_password` function: ``` def validate_current_password(changeset, password) do + changeset = cast(changeset, %{current_password: password}, [:current_password]) + if valid_password?(changeset.data, password) do ``` ## [](changelog.html#1-0-2-2025-01-09)1.0.2 (2025-01-09) ### [](changelog.html#bug-fixes)Bug fixes - Fix inconsistency between [`mix format`](../mix/Mix.Tasks.Format.html) and `mix format --check-formatted` with new curly interpolation syntax ([#3590](https://github.com/phoenixframework/phoenix_live_view/issues/3590)) - Fix unnecessary compile time dependencies when using `attr` / `on_mount` / `live` ([#3592](https://github.com/phoenixframework/phoenix_live_view/issues/3592)) - Fix crash when testing LiveViews with embedded XML (e.g. SVGs) ([#3594](https://github.com/phoenixframework/phoenix_live_view/issues/3594)) - Fix type warning when using `follow_redirect` ([#3581](https://github.com/phoenixframework/phoenix_live_view/issues/3581)) - Prevent `phx-trigger-action` from clashing with locked forms ([#3591](https://github.com/phoenixframework/phoenix_live_view/issues/3591)) - Fix form recovery sending wrong event name when using JS commands in `phx-change` ([#3607](https://github.com/phoenixframework/phoenix_live_view/issues/3607)) ### [](changelog.html#enhancements)Enhancements - Deduplicate items on `stream/4` / `steam_insert/4` ([#3599](https://github.com/phoenixframework/phoenix_live_view/pull/3599)) - Restore scroll position on initial navigation ([#3572](https://github.com/phoenixframework/phoenix_live_view/pull/3572)) - Change-track non existing keys in maps ([#3584](https://github.com/phoenixframework/phoenix_live_view/pull/3584)) - Only warn instead of raising when detecting a duplicate ID in LiveViewTest ([#3603](https://github.com/phoenixframework/phoenix_live_view/pull/3603)) ## [](changelog.html#1-0-1-2024-12-13)1.0.1 (2024-12-13) ### [](changelog.html#bug-fixes-1)Bug fixes - Raise when duplicate DOM IDs are found when rendering a LiveView during tests to avoid undefined behaviour - Fix live session verification causing logged errors, push\_patch failures, and failed mounts when a cold deploy occurs ## [](changelog.html#1-0-0-2024-12-03)1.0.0 (2024-12-03) 🚀 ## [](changelog.html#1-0-0-rc-9-2024-12-03)1.0.0-rc.9 (2024-12-03) ### [](changelog.html#enhancements-1)Enhancements - Support `phx-no-curly-interpolation` to disable HEEx curly interpolation in a specific tag body ## [](changelog.html#1-0-0-rc-8-2024-12-02)1.0.0-rc.8 (2024-12-02) ### [](changelog.html#backwards-incompatible-changes)Backwards incompatible changes - Require Elixir v1.14.1+ ### [](changelog.html#bug-fixes-2)Bug fixes - Fix live navigation issue where LiveView would attempt a patch on back navigation instead of navigate under certain routing conditions - Fix live upload issue where large `chunk_size` could cause a chunk timeout on slower connections by properly using the configured `chunk_timeout` on the client - Fix focused inputs losing focus state in some update scenarios - Fix rare `no component for cid` error when rapidly updating live components under latency - Fix browser back/forward buttons using `patch` instead of `navigate`, failing to update the page - Fix `inputs_for` not using the passed `id` when generating inputs - Do not throw an exception when dispatching a custom input event outside of JS.dispatch - Fix submitter not being sent when using `JS.push` - Fix form action not being inherited by `to_form` when passing an existing form - Fix live\_session not being enforced when patching to the same LiveView under a different route ### [](changelog.html#enhancements-2)Enhancements - Extend HEEx `{@foo}` syntax for interpolating variables inside tag bodies - Support `default` attribute for `<.live_title>` component - Add `refute_push_event` to [`Phoenix.LiveViewTest`](Phoenix.LiveViewTest.html) - Keep intentional newlines when formatting comments (#3486) - Support hook `pushEvent` returning a promise - Log a warning when a `push_navigate` fallbacks to a full page reload ## [](changelog.html#1-0-0-rc-7-2024-10-17)1.0.0-rc.7 (2024-10-17) ### [](changelog.html#enhancements-3)Enhancements - Support nested inputs in [`Phoenix.Component.used_input?/1`](Phoenix.Component.html#used_input?/1) - Support custom redirect statuses on [`Phoenix.LiveView.redirect/2`](Phoenix.LiveView.html#redirect/2) - Export `createHook` from `phoenix_live_view` to allow CustomElement's to use hooks - Expose programmable JS command interface to hooks for showing, hiding, addClass, etc from a hook which integrates with server DOM patching - Support targeting inner and closest query selectors in JS commands with `to: {:inner, "a"}` and `to: {:closest, "tr"}`, etc. - Throw a more informative error when `JS.dispatch/2` is used with an input event on an invalid target - Validate slot options (prevents typos like `slot :myslot, requird: true`) - Emit `:phoenix, :live_component, :update` telemetry event when a LiveComponent is updated via `send_update/2` - Enhance error recovery during connecting mount to trigger plug\_status error pipeline ### [](changelog.html#bug-fixes-3)Bug fixes - Fix `phx-click-loading` classes being incorrectly applied to child elements when issues from a non user click/navigation event - Fix hooks on root LiveComponent container not being torn down and created when the DOM id changes - Fix select focus states and selects not being updated when focused #3083, #3107 - Fix nested LiveViews inside a stream element - Fix infinite loading streams in zoomed viewports #3442 - Fix race condition in latency simulator causing messages to be applied out of order - Fix stream items not reapplying JS commands when joining after a disconnect - Fix unnecessary remount when navigating back - Fix character composition mode not working properly in Safari when an input is patched - Fix `cannot redirect socket on update` error raised erroneously under rare circumstances - Fix upstream morphdom issue that could cause elements being removed from the real DOM instead of a cloned tree while some elements are locked - Don't dispatch click event when moving mouse away from initial click target - Fix formatter formatting expressions that are strings with escaped quotes incorrectly ### [](changelog.html#deprecations)Deprecations - Deprecate the `name` attribute of [`Phoenix.Component.dynamic_tag/1`](Phoenix.Component.html#dynamic_tag/1) in favor of `tag_name` ## [](changelog.html#1-0-0-rc-6-2024-06-27)1.0.0-rc.6 (2024-06-27) ### [](changelog.html#bug-fixes-4)Bug fixes - Fix issue with phx-submit where the form container would not be updated while awaiting phx-submit acknowledgement ### [](changelog.html#enhancements-4)Enhancements - Support `blocking: false` option to all JS commands that accept `:transition`, to avoid blocking DOM updates during the transition ## [](changelog.html#1-0-0-rc-5-2024-06-21)1.0.0-rc.5 (2024-06-21) ### [](changelog.html#bug-fixes-5)Bug fixes - Fix form recovery when targeting LiveComponents - Fix formatter incorrectly formatting script tags - Fix client version mismatch warning for rc.3 ## [](changelog.html#1-0-0-rc-4-2024-06-20)1.0.0-rc.4 (2024-06-20) ### [](changelog.html#bug-fixes-6)Bug fixes - Fix bug allowing focused input to be overwritten on acknowledgement ## [](changelog.html#1-0-0-rc-3-2024-06-20)1.0.0-rc.3 (2024-06-20) ### [](changelog.html#bug-fixes-7)Bug fixes - Fix issue where locked data-phx-ref containers would still be patched in some cases - Fix issue where locked data-phx-ref containers would fail to have pending updates like streams applied after acknowledgment - Fix issue where a LiveComponent issuing live navigation sends an acknowledgement too early before its parent LiveView handle\_params diff is sent - Fix issue allowing phx-remove elements to remain interactive while transitioning out from the page ## [](changelog.html#1-0-0-rc-2-2024-06-19)1.0.0-rc.2 (2024-06-19) ### [](changelog.html#bug-fixes-8)Bug fixes - Fix bug introduced in rc.1 causing incorrect patching on form elements when awaiting acknowledgements ## [](changelog.html#1-0-0-rc-1-2024-06-18)1.0.0-rc.1 (2024-06-18) ### [](changelog.html#enhancements-5)Enhancements - Warn when rendering an input named "id" - Allow form recovery to work on forms in nested LiveViews - Allow using form/3 with inputs outside the form - Allow setting page title to an empty string - Fix warnings on Elixir 1.17 ### [](changelog.html#bug-fixes-9)Bug fixes - Fix used input tracking on checkboxes and hidden inputs - Fix phx-debounce=blur incorrectly sending change event to the next page in some circumstances - Fix race condition when destroying Live Components while transitions are still running - Fix page reload when disconnecting LiveSocket if using Bandit - Fix formatter changing `<%` to `<%=` when it shouldn't ### [](changelog.html#deprecations-1)Deprecations - Deprecate `live_flash` in favor of `Phoenix.Flash.get` - Deprecate `push_redirect` in favor of `push_navigate` ### [](changelog.html#removal-of-previously-deprecated-functionality)Removal of previously deprecated functionality - `phx-capture-click` has been removed (deprecated in 0.17.0) ## [](changelog.html#1-0-0-rc-0-2024-05-08)1.0.0-rc.0 🚀 (2024-05-08) ### [](changelog.html#backwards-incompatible-changes-1)Backwards incompatible changes - Remove `phx-feedback-for` in favor of `Phoenix.Component.used_input?`. See the changelog for a backwards compatible `phx-feedback-for` shim to add to existing applications. ### [](changelog.html#removal-of-previously-deprecated-functionality-1)Removal of previously deprecated functionality - `live_component/2` and `live_component/3` helpers (not the function component) have been removed ### [](changelog.html#bug-fixes-10)Bug fixes - Fix attributes of existing stream items not being updated on reset - Fix nested LiveView within streams becoming empty when reset - Fix `phx-mounted` firing twice, first on dead render, then on live render, leading to errors when a LiveComponent has not yet mounted - Fix `JS.toggle_class` error when used with a transition - Fix updated form values not being sent in some circumstances when phx-trigger-action is used - Fix phx-viewport bindings when html or body element has overflow property ### [](changelog.html#enhancements-6)Enhancements - Warn on mismatched client and server versions ## [](changelog.html#0-20-16-2024-06-20)0.20.16 (2024-06-20) ### [](changelog.html#bug-fixes-11)Bug fixes - Fix bug introduced in 0.20.15 causing incorrect patching on form elements when awaiting acknowledgements ## [](changelog.html#0-20-15-2024-06-18)0.20.15 (2024-06-18) ### [](changelog.html#enhancements-7)Enhancements - Warn when rendering an input named "id" - Warn on mismatched client and server versions - Allow form recovery to work on forms in nested LiveViews - Allow using form/3 with inputs outside the form - Allow setting page title to an empty string - Fix warnings on Elixir 1.17 ### [](changelog.html#bug-fixes-12)Bug fixes - Fix attributes of existing stream items not being updated on reset - Fix nested LiveView within streams becoming empty when reset - Fix `phx-mounted` firing twice, first on dead render, then on live render, leading to errors when a LiveComponent has not yet mounted - Fix `JS.toggle_class` error when used with a transition - Fix phx-debounce=blur incorrectly sending change event to the next page in some circumstances - Fix race condition when destroying Live Components while transitions are still running - Fix page reload when disconnecting LiveSocket if using Bandit - Fix formatter changing `<%` to `<%=` when it shouldn't - Fix updated form values not being sent in some circumstances when phx-trigger-action is used - Fix phx-viewport bindings when html or body element has overflow property ### [](changelog.html#deprecations-2)Deprecations - Deprecate `live_flash` in favor of `Phoenix.Flash.get` - Deprecate `push_redirect` in favor of `push_navigate` ### [](changelog.html#removal-of-previously-deprecated-functionality-2)Removal of previously deprecated functionality - `phx-capture-click` has been removed (deprecated in 0.17.0) - `live_component/2` and `live_component/3` helpers (not the function component) have been removed ## [](changelog.html#0-20-14-2024-03-13)0.20.14 (2024-03-13) ### [](changelog.html#bug-fixes-13)Bug fixes - Fix warning caused by optional Floki dependency ## [](changelog.html#0-20-13-2024-03-12)0.20.13 (2024-03-12) ### [](changelog.html#bug-fixes-14)Bug fixes - Fix LiveComponent rendering bug causing elements to disappear when a LiveComponent child is removed and added back by the server ### [](changelog.html#enhancements-8)Enhancements - Warn when accessing the socket in a function passed to assign\_async / start\_async ## [](changelog.html#0-20-12-2024-03-04)0.20.12 (2024-03-04) ### [](changelog.html#enhancements-9)Enhancements - Phoenix LiveView requires Elixir v1.13+ ### [](changelog.html#bug-fixes-15)Bug fixes - Do not send Telemetry metadata as Logger event, this avoids the metadata from being accidentally copied to other processes - Ensure LiveViewTest does not crash on IDs with foreign characters, such as question marks - Fix a bug where LiveViewTest could not perform a connected mount on a page with certain streams ## [](changelog.html#0-20-11-2024-02-29)0.20.11 (2024-02-29) ### [](changelog.html#bug-fixes-16)Bug fixes - Fix auto uploads with invalid entries incorrectly proceeding with a form submit instead of halting, causing entries in progress errors - Fix auto upload entries failing to be uploaded on submit after moving into a valid state, such as falling within `max_entries` - Fix TagEngine clause warning ## [](changelog.html#0-20-10-2024-02-28)0.20.10 (2024-02-28) ### [](changelog.html#bug-fixes-17)Bug fixes - Fix cancelled uploads being re-added - Fix form submits for previously submitted invalid upload entries causing errors instead of preflighted new upload entries - Fix HTML formatter not respecting phx-no-format for script tags ### [](changelog.html#enhancements-10)Enhancements - Add additional HEEx debug annotations for the caller locations of function component invocations - Abandon form recovery if recovery event fails ## [](changelog.html#0-20-9-2024-02-19)0.20.9 (2024-02-19) ### [](changelog.html#bug-fixes-18)Bug fixes - Fix error in LiveViewTest when a `phx-update="ignore"` container is added dynamically to the DOM ## [](changelog.html#0-20-8-2024-02-19)0.20.8 (2024-02-19) ### [](changelog.html#bug-fixes-19)Bug fixes - Fix live uploader issue when a form contained more than one `<.live_file_input>` - Fix phx-remove on re-added stream items trigging the remove when it should not - Fix js error attempting to re-order an element that does not exist in the DOM ### [](changelog.html#enhancements-11)Enhancements - Align LiveViewTest with JavaScript DOM patching behavior for phx-update="ignore" when updating attributes in LiveViewTest ## [](changelog.html#0-20-7-2024-02-15)0.20.7 (2024-02-15) ### [](changelog.html#bug-fixes-20)Bug fixes - Fix phx-skip containers leaking into DOM on first patch in some cases (#3117) - Fix phx-feedback-for failing to be properly updated in some cases (#3122) ## [](changelog.html#0-20-6-2024-02-14)0.20.6 (2024-02-14) ### [](changelog.html#bug-fixes-21)Bug fixes - Fix stream items being excluded in LiveViewTest - Fix stream items failing to properly update nested streams or LiveComponents - Fix debounce/blur regression causing unexpected events to be sent ## [](changelog.html#0-20-5-2024-02-08)0.20.5 (2024-02-08) ### [](changelog.html#deprecations-3)Deprecations - Deprecate `phx-feedback-group` introduced in the previous release, the goal is to move feedback handling into Elixir and out of the DOM ### [](changelog.html#bug-fixes-22)Bug fixes - Fix blur event on phx-debounce being dispatched incorrectly - Fix `open_browser` not working on WSL for project under UNIX file systems - Match browser stream insert ordering behavior in `LiveViewTest` - Fix `phx-click-away` not working when element is partially hidden - Fix `phx-feedback-for` classes not being applied in some cases - Fix form submitter failing to be sent as key/value pair in some cases - Fix null form reference causing errors on some DOM patches ## [](changelog.html#0-20-4-2024-02-01)0.20.4 (2024-02-01) ### [](changelog.html#bug-fixes-23)Bug fixes - Fix phx-remove on sticky LiveViews - Fix phx-disabled-with not restoring button disabled state on empty diff acknowledgement - Fix stream reset not ordering items correctly - Send `{:shutdown, :cancel}` to `handle_async/3` on `cancel_async` - Prevent events from child LiveViews from bubbling up to root LiveView when child is not mounted yet - Fix `phx-mounted` being called twice for stream items - Never move existing stream items if they already exist (use stream\_delete and then stream\_insert instead) - Fix live component rendering breaking when the server adds a component back that was pruned by the client (#3026) - Allow redirect from upload progress callback - Fix nested components getting skipped when resetting a stream - Fix nested components getting skipped in LiveComponents - Fix stream limits not being applied correctly when bulk inserting - Fix click-away being called incorrectly on form submits - Fix inconsistencies between LiveViewTest and browser stream implementations - Fix phx-feedback-for being reapplied when there are multiple inputs with the same name - Ensure phx-update="ignore" behaves consistently: updates from the server to the element's content and attributes are ignored, *except for data attributes* ### [](changelog.html#enhancements-12)Enhancements - Add `JS.toggle_class` - Add `JS.toggle_attribute` - Force update select options when the options changed from the server while a select has focus - Introduce `phx-feedback-group` for handling feedback for composite input groups - Add `validate_attrs` to slots - Support `phx-viewport` bindings in scrollable containers - Perform client redirect when trying to live nav from dead client to avoid extra round trip - Allow regular buttons with name/value attributes to send form events and adjusted dynamic form documentation to reflect this - Allow form attribute on `live_file_input` ### [](changelog.html#removal-of-previously-deprecated-functionality-3)Removal of previously deprecated functionality - `live_component/2` and `live_component/3` helpers (not the function component) have been removed ## [](changelog.html#0-20-3-2024-01-02)0.20.3 (2024-01-02) ### [](changelog.html#bug-fixes-24)Bug fixes - Fix phx-viewport bindings failing to fire after navigation - Preserve order of appended items in stream in `LiveViewTest` - Fix order of items on client when resetting a stream to existing set of items ### [](changelog.html#enhancements-13)Enhancements - Support `JS.push` from dead views ## [](changelog.html#0-20-2-2023-12-18)0.20.2 (2023-12-18) ### [](changelog.html#bug-fixes-25)Bug fixes - Fix JavaScript error when submitting a form that has in progress uploads - Fix JS command `:target` failing to work when used as phx-submit or phx-change with a selector-based target - Fix `JS.focus()` failing to focus negative tabindex - Fix `LiveViewTest` failing to remove items after stream reset - Fix `phx-window-blur` and `phx-window-focus` events not firing - Fix SVG anchor links throwing errors when clicked ### [](changelog.html#enhancements-14)Enhancements - Speed up DOM patching performance 3-30x 🔥 - Support `handle_async` lifecycle callback - Extend visibility checks for phx-click-away to better handle whether an element is visible in the viewport or not - Allow `JS.patch` and `JS.navigate` to be tested with `render_click` - Support `:supervisor` option to `assign_async` and `start_async` ### [](changelog.html#deprecations-4)Deprecations - Deprecate `phx-update="append"` and `phx-update="prepend"` in favor of `phx-update="stream"` ## [](changelog.html#0-20-1-2023-10-09)0.20.1 (2023-10-09) ### [](changelog.html#bug-fixes-26)Bug fixes - Fix error with live uploads `auto_upload: true` when a file fails to preflight - Fix error with live uploads where an early exit can cause a map key error - Fix match error on live navigation when reconnecting from client ### [](changelog.html#enhancements-15)Enhancements - Support new `meta()` method on File/Blob subclasses on JavaScript client to allow the client to pass arbitrary metadata when using `upload/uploadTo` from hook. The `%UploadEntry{}`'s new `client_meta` field is populated from this information. - Improve void tagging and error messages ## [](changelog.html#0-20-0-2023-09-22)0.20.0 (2023-09-22) ### [](changelog.html#deprecations-5)Deprecations - Deprecate the `~L` sigil in favor of `~H` - Deprecate `preload/1` in LiveComponent in favor of `update_many/1` - Deprecate `live_component/2-3` in favor of `<.live_component />` - Deprecate `live_patch` in favor of `<.link patch={...} />` - Deprecate `live_redirect` in favor of `<.link navigate={...} />` - Deprecate `live_title_tag` in favor of `<.live_title />` ### [](changelog.html#backwards-incompatible-changes-2)Backwards incompatible changes - Remove previously deprecated `render_block/2` in favor of `render_slot/2` - Remove previously deprecated `live_img_preview/2` in favor of `<.live_img_preview />` - Remove previously deprecated `live_file_input/2` in favor of `<.live_file_input />` ### [](changelog.html#bug-fixes-27)Bug fixes - Fix uploads with `auto_upload: true` failing to propagate errors when any individual entry is invalid - Fix uploads with `auto_upload: true` failing to auto upload valid entries errors when any individual entry is invalid - Fix error on form recovery with `auto_upload: true` - Fix issue on form recovery where hidden inputs would be selected by mistake - Fix form recovery when phx-change is a JS command - Fix stream reset on nested live components with nested streams - Fix window location resetting to null when using nested LiveView on connection error - Fix anchors within contenteditable causing LiveSocket disconnects ### [](changelog.html#enhancements-16)Enhancements - Add heex debug annotations via `config :phoenix_live_view, debug_heex_annotations: true`, which provides special HTML comments that wrap around rendered components to help you identify where markup in your HTML document is rendered within your function component tree - Add `assign_async`, `start_async`, `<.async_result>` and, `AsyncResult` for declaratively handling async operations in a LiveView or LiveComponent. - Supporting passing `@myself` for [`Phoenix.LiveView.send_update/3`](Phoenix.LiveView.html#send_update/3) - Support change tracking on Access.get - Allow overriding `id` of `<.live_img_preview>` ## [](changelog.html#0-19-5-2023-07-19)0.19.5 (2023-07-19) ### [](changelog.html#backwards-incompatible-changes-3)Backwards incompatible changes - The `close/1` callback of [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) is now `close/2` with the close reason passed as the second argument. - The `write_chunk` callback of [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) must now return the updated writer state when an error occurs. Instead of `{:error, reason}`, return `{:error, reason, new_state}`. ### [](changelog.html#enhancements-17)Enhancements - Pass close reason to [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) close. - Dispatch `phx:navigate` window events when LiveView changes the history state ### [](changelog.html#bug-fixes-28)Bug fixes - Call [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) close callback when LiveView goes down or connection is lost - Fix JS.patch to a Phoenix router scope with `:host` causing errors - Fix immediate navigation after patch not updating URL - Fix stream reset on nested streams inside live components causing nested stream children to be removed ## [](changelog.html#0-19-4-2023-07-10)0.19.4 (2023-07-10) ### [](changelog.html#enhancements-18)Enhancements - Introduce [`Phoenix.LiveView.UploadWriter`](Phoenix.LiveView.UploadWriter.html) ## [](changelog.html#0-19-3-2023-06-21)0.19.3 (2023-06-21) ### [](changelog.html#bug-fixes-29)Bug fixes - Fix `push_event` inside component update not being sent in some cases - Bring back accidentally deprecated `upload_errors/1` ## [](changelog.html#0-19-2-2023-06-12)0.19.2 (2023-06-12) ### [](changelog.html#bug-fixes-30)Bug fixes - Fix issue when `<input name="value" />` is used ## [](changelog.html#0-19-1-2023-06-06)0.19.1 (2023-06-06) ### [](changelog.html#enhancements-19)Enhancements - Allow `accept` attribute on `<.live_file_input>` to override default values ### [](changelog.html#bug-fixes-31)Bug fixes - Fix issue causing anchor clicks to disconnect LV when they were already handled via `preventDefault()` by other scripts ## [](changelog.html#0-19-0-2023-05-29)0.19.0 (2023-05-29) ### [](changelog.html#backwards-incompatible-changes-4)Backwards incompatible changes - Drop support for passing an id to the `phx-feedback-for` attribute. An input name must be passed instead. - Remove previously deprecated `let` attribute. Use `:let` instead - Remove previously deprecated `<%= live_img_preview(entry) %>`. Use `<.live_img_preview entry={entry} />` instead - Remove previously deprecated `<%= live_file_input(upload) %>`. Use `<.live_file_input upload={upload} />` instead - Remove previously deprecated `<%= live_component(Component) %>`. Use `<.live_component module={Component} id=\"hello\" />` instead - Don't convert underscores to dashes automatically when rendering HTML attributes. Use dashes or underscores where appropriate instead. ### [](changelog.html#enhancements-20)Enhancements - Support stream resets with bulk insert operations - Support ordered inputs within `inputs_for`, to pair with Ecto's new `sort_param` and `drop_param` casting - Send form phx-value's on form events ### [](changelog.html#deprecations-6)Deprecations - Deprecate passing `:dom_id` to `stream/4` in favor of `stream_configure/3` - Deprecate `render_block/2` in favor of `render_slot/2` - Deprecate `<%= live_img_preview(entry, opts) %>`. Use `<.live_img_preview entry={entry} {opts} />` - Deprecate `<%= live_file_input(upload, opts) %>`. Use `<.live_file_input upload={upload} {opts} />` - Deprecate stateless LiveComponent in favor of function components or in favor of `<.live_component id={...} />` (note the `id` is required) ### [](changelog.html#bug-fixes-32)Bug fixes - Fix LiveView disconnects when clicking a `download` link - Fix stream deletes not being sent on nested for comprehensions - Fix `phx-disconnected` bindings not firing on mount failures - Support form recovery on forms with only hidden inputs ## [](changelog.html#0-18-18-2023-03-16)0.18.18 (2023-03-16) ### [](changelog.html#bug-fixes-33)Bug fixes - Allow `:live_action` to be assigned in a component - Only filter internal function component attributes in `assigns_to_attributes` - Only include submitter with name ### [](changelog.html#enhancements-21)Enhancements - Add `JS.exec` command for executing commands defined on other element attributes ## [](changelog.html#0-18-17-2023-03-09)0.18.17 (2023-03-09) ### [](changelog.html#bug-fixes-34)Bug Fixes - Fix callbacks like `handle_info` failing to be invoked in development after a code change with the Phoenix code reloader ### [](changelog.html#enhancements-22)Enhancements - Support [`submitter`](https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent/submitter) on form submit events. - Avoid compile-time dependency for `attr` when referencing structs - Validate reserved assigns. Attempting to assign `:uploads`, `:streams`, `:live_action`, `:socket`, `:myself` will now raise in LiveView and LiveComponent ## [](changelog.html#0-18-16-2023-02-23)0.18.16 (2023-02-23) ### [](changelog.html#enhancements-23)Enhancements - Support streams in Live Components - Optimize plug error translation when a Plug.Exception is raised over connected LiveView ### [](changelog.html#bug-fixes-35)Bug Fixes - Fix formatter issues when there are multiple HTML comments ## [](changelog.html#0-18-15-2023-02-16)0.18.15 (2023-02-16) ### [](changelog.html#bug-fixes-36)Bug Fixes - Fix `JS.transition` applying incorrect classes ### [](changelog.html#enhancements-24)Enhancements - Reset phx-feedback-for errors on `type="reset"` inputs and buttons ## [](changelog.html#0-18-14-2023-02-14)0.18.14 (2023-02-14) ### [](changelog.html#bug-fixes-37)Bug Fixes - Fix LiveViewTest failing to find main live view ## [](changelog.html#0-18-13-2023-02-10)0.18.13 (2023-02-10) ### [](changelog.html#enhancements-25)Enhancements - Improve error message when failing to use Phoenix.Component ## [](changelog.html#0-18-12-2023-02-10)0.18.12 (2023-02-10) ### [](changelog.html#enhancements-26)Enhancements - Introduce streams for efficiently handling large collections - Allow replies from `:handle_event` lifecycle hooks - Add `<.inputs_for>` component to [`Phoenix.Component`](Phoenix.Component.html) - Support replies on lifecycle `:handle_event` hooks ### [](changelog.html#bug-fixes-38)Bug Fixes - Fix change tracking when re-assigning a defaulted attribute to same default value - Fix upload drag and drop failing to worka after using file select dialog - Fix form recovery when form's first input is phx-change ## [](changelog.html#0-18-11-2023-01-19)0.18.11 (2023-01-19) ### [](changelog.html#bug-fixes-39)Bug Fixes - Fix socket unloading connection for forms that have defaulted prevented ## [](changelog.html#0-18-10-2023-01-18)0.18.10 (2023-01-18) ### [](changelog.html#bug-fixes-40)Bug Fixes - Fix svg tags with href incorrectly unloading socket on click - Fix form submits with `target="_blank"` incorrectly unloading socket on submit ## [](changelog.html#0-18-9-2023-01-17)0.18.9 (2023-01-17) ### [](changelog.html#bug-fixes-41)Bug Fixes - Fix regular form submits failing to be dispatched ## [](changelog.html#0-18-8-2023-01-16)0.18.8 (2023-01-16) ### [](changelog.html#enhancements-27)Enhancements - Restore scroll position on back when previous navigation was live patch ### [](changelog.html#bug-fixes-42)Bug Fixes - Fix live layout not being applied until connected render ## [](changelog.html#0-18-7-2023-01-13)0.18.7 (2023-01-13) ### [](changelog.html#bug-fixes-43)Bug Fixes - Fix live layout not being applied when passed to `:live_session` during disconnect render - Fix external anchor clicks and links with hashes incorrectly unloading socket ## [](changelog.html#0-18-6-2023-01-09)0.18.6 (2023-01-09) ### [](changelog.html#bug-fixes-44)Bug Fixes - Fix external anchor click unloading on external click ## [](changelog.html#0-18-5-2023-01-09)0.18.5 (2023-01-09) ### [](changelog.html#bug-fixes-45)Bug Fixes - Fix external anchor click unloading socket ## [](changelog.html#0-18-4-2023-01-05)0.18.4 (2023-01-05) ### [](changelog.html#enhancements-28)Enhancements - Support string upload name to support dynamically generated `allow_upload`'s ### [](changelog.html#bug-fixes-46)Bug Fixes - Fix nested LiveView race condition on live patch causing nested child to skip updates in some cases - Fix browser history showing incorrect title when using live navigation with `@page_title` - Fix undefined \_target param when using `JS.push` for form changes - Fix `phx-no-feedback` missing from inputs added after a form submit - Fix `phx-disconnected` events firing when navigating away or submitting external forms ## [](changelog.html#0-18-3-2022-10-26)0.18.3 (2022-10-26) ### [](changelog.html#enhancements-29)Enhancements - Add `embed_templates` to [`Phoenix.Component`](Phoenix.Component.html) for embedding template files as function components - Raise on global slot attributes ### [](changelog.html#bug-fixes-47)Bug Fixes - Fix bug on slots when passing multiple slot entries with mix if/for syntax ## [](changelog.html#0-18-2-2022-10-04)0.18.2 (2022-10-04) ### [](changelog.html#bug-fixes-48)Bug Fixes - Fix match error when defining `:values` before `:default` - Allow tuples in external redirects - Fix race condition on dispatching click away when enter is pressed - Fix formatter breaking inline blocks when surrounded by text without whitespace ### [](changelog.html#enhancements-30)Enhancements - Add `intersperse` component for rendering a separator between an enumerable ## [](changelog.html#0-18-1-2022-09-28)0.18.1 (2022-09-28) ### [](changelog.html#bug-fixes-49)Bug Fixes - Fix phx-loading class being applied to dead views - Fix `<.live_img_preview />` causing invalid attribute errors on uploads - Do not fire phx events when element is disabled ### [](changelog.html#enhancements-31)Enhancements - Support `:include` option to extend global attributes on a case-by-case basis - Warn when accessing a variable binding defined outside of `~H` ## [](changelog.html#0-18-0-2022-09-20)0.18.0 (2022-09-20) LiveView v0.18 includes a major new feature in the form of declarative assigns with new `attr` and `slot` APIs for specifying which attributes a function component supports, the type, and default values. Attributes and slots are compile-time verified and emit warnings (requires Elixir v1.14.0+). v0.18 includes a number of new function components which replace their EEx expression counterparts `<%= ... %>`. For example, `live_redirect`, `live_patch`, and Phoenix.HTML's `link` have been replaced by a unified [`Phoenix.Component.link/1`](Phoenix.Component.html#link/1) function component: ``` <.link href="https://myapp.com">my app</.link> <.link navigate={@path}>remount</.link> <.link patch={@path}>patch</.link> ``` Those new components live in the [`Phoenix.Component`](Phoenix.Component.html) module. `Phoenix.LiveView.Helpers` itself has been soft deprecated and all relevant functionality has been migrated. You must `import Phoenix.Component` where you previously imported `Phoenix.LiveView.Helpers` when upgrading. You may also need to `import Phoenix.Component` where you also imported [`Phoenix.LiveView`](Phoenix.LiveView.html) and some of its functions have been moved to [`Phoenix.Component`](Phoenix.Component.html). Additionally, the special `let` attribute on function components have been deprecated by a `:let` usage. ### [](changelog.html#deprecations-7)Deprecations - `live_redirect` - deprecate in favor of new `<.link navigate={..}>` component of [`Phoenix.Component`](Phoenix.Component.html) - `live_patch` - deprecate in favor of new `<.link patch={..}>` component of [`Phoenix.Component`](Phoenix.Component.html) - `push_redirect` - deprecate in favor of new `push_navigate` function on [`Phoenix.LiveView`](Phoenix.LiveView.html) ### [](changelog.html#enhancements-32)Enhancements - \[Component] Add declarative assigns with compile-time verifications and warnings via `attr`/`slot` - \[Component] Add new attrs `:let` and `:for`, and `:if` with HTML tag, function component, and slot support. We still support `let` but the formatter will convert it to `:let` and soon it will be deprecated. - \[Component] Add `dynamic_tag` function component - \[Component] Add `link` function component - \[Component] Add `focus_wrap` function component to wrap focus around content like modals and dialogs for accessibility - \[Logger] Add new LiveView logger with telemetry instrumentation for lifecycle events - \[JS] Add new JS commands for `focus`, `focus_first`, `push_focus`, and `pop_focus` for accessibility - \[Socket] Support sharing [`Phoenix.LiveView.Socket`](Phoenix.LiveView.Socket.html) with regular channels via `use Phoenix.LiveView.Socket` - Add `_live_referer` connect param for handling `push_navigate` referral URL - Add new `phx-connected` and `phx-disconnected` bindings for reacting to lifecycle changes - Add dead view support for JS commands - Add dead view support for hooks ### [](changelog.html#bug-fixes-50)Bug fixes - Fix external upload issue where listeners are not cleaned up when an external failure happens on the client - Do not debounce `phx-blur` ## [](changelog.html#0-17-12-2022-09-20)0.17.12 (2022-09-20) ### [](changelog.html#enhancements-33)Enhancements - Add support for upcoming Phoenix 1.7 flash interface ## [](changelog.html#0-17-11-2022-07-11)0.17.11 (2022-07-11) ### [](changelog.html#enhancements-34)Enhancements - Add `replaceTransport` to LiveSocket ### [](changelog.html#bug-fixes-51)Bug fixes - Cancel debounced events from firing after a live navigation event - Fix hash anchor failing to scroll to anchor element on live navigation - Do not debounce `phx-blur` events ## [](changelog.html#0-17-10-2022-05-25)0.17.10 (2022-05-25) ### [](changelog.html#bug-fixes-52)Bug fixes - \[Formatter] Preserve single quote delimiter on attrs - \[Formatter] Do not format inline elements surrounded by texts without whitespaces - \[Formatter] Keep text and eex along when there isn't a whitespace - \[Formatter] Fix intentional line breaks after eex expressions - \[Formatter] Handle self close tags as inline - \[Formatter] Do not format inline elements without whitespaces among them - \[Formatter] Do not format when attr contenteditable is present ### [](changelog.html#enhancements-35)Enhancements - \[Formatter] Introduce special attr phx-no-format to skip formatting ## [](changelog.html#0-17-9-2022-04-07)0.17.9 (2022-04-07) ### [](changelog.html#bug-fixes-53)Bug fixes - Fix sticky LiveViews failing to be patched during live navigation - Do not raise on dynamic `phx-update` value ## [](changelog.html#0-17-8-2022-04-06)0.17.8 (2022-04-06) ### [](changelog.html#enhancements-36)Enhancements - Add HEEx formatter - Support `phx-change` on individual inputs - Dispatch `MouseEvent` on client - Add `:bubbles` option to `JS.dispatch` to control event bubbling - Expose underlying `liveSocket` instance on hooks - Enable client debug by default on localhost ### [](changelog.html#bug-fixes-54)Bug fixes - Fix hook and sticky LiveView issues caused by back-to-back live redirects from mount - Fix hook destroyed callback failing to be invoked for children of phx-remove in some cases - Do not failsafe reload the page on push timeout if disconnected - Do not bubble navigation click events to regular phx-click's - No longer generate `csrf_token` for forms without action, reducing the payload during phx-change/phx-submit events ## [](changelog.html#0-17-7-2022-02-07)0.17.7 (2022-02-07) ### [](changelog.html#enhancements-37)Enhancements - Optimize nested for comprehension diffs ### [](changelog.html#bug-fixes-55)Bug fixes - Fix error when `live_redirect` links are clicked when not connected in certain cases ## [](changelog.html#0-17-6-2022-01-18)0.17.6 (2022-01-18) ### [](changelog.html#enhancements-38)Enhancements - Add `JS.set_attribute` and `JS.remove_attribute` - Add `sticky: true` option to `live_render` to maintain a nested child on across live redirects - Dispatch `phx:show-start`, `phx:show-end`, `phx:hide-start` and `phx:hide-end` on `JS.show|hide|toggle` - Add `get_connect_info/2` that also works on disconnected render - Add `LiveSocket` constructor options for configuration failsafe behavior via new `maxReloads`, `reloadJitterMin`, `reloadJitterMax`, `failsafeJitter` options ### [](changelog.html#bug-fixes-56)Bug fixes - Show form errors after submit even when no changes occur on server - Fix `phx-disable-with` failing to disable elements outside of forms - Fix phx ref tracking leaving elements in awaiting state when targeting an external LiveView - Fix diff on response failing to await for active transitions in certain cases - Fix `phx-click-away` not respecting `phx-target` - Fix "disconnect" broadcast failing to failsafe refresh the page - Fix `JS.push` with `:target` failing to send to correct component in certain cases ### [](changelog.html#deprecations-8)Deprecations - Deprecate `Phoenix.LiveView.get_connect_info/1` in favor of `get_connect_info/2` - Deprecate `Phoenix.LiveViewTest.put_connect_info/2` in favor of calling the relevant functions in [`Plug.Conn`](../plug/1.16.1/Plug.Conn.html) - Deprecate returning "raw" values from upload callbacks on [`Phoenix.LiveView.consume_uploaded_entry/3`](Phoenix.LiveView.html#consume_uploaded_entry/3) and [`Phoenix.LiveView.consume_uploaded_entries/3`](Phoenix.LiveView.html#consume_uploaded_entries/3). The callback must return either `{:ok, value}` or `{:postpone, value}`. Returning any other value will emit a warning. ## [](changelog.html#0-17-5-2021-11-02)0.17.5 (2021-11-02) ### [](changelog.html#bug-fixes-57)Bug fixes - Do not trigger `phx-click-away` if element is not visible - Fix `phx-remove` failing to tear down nested live children ## [](changelog.html#0-17-4-2021-11-01)0.17.4 (2021-11-01) ### [](changelog.html#bug-fixes-58)Bug fixes - Fix variable scoping issues causing various content block or duplication rendering bugs ## [](changelog.html#0-17-3-2021-10-28)0.17.3 (2021-10-28) ### [](changelog.html#enhancements-39)Enhancements - Support 3-tuple for JS class transitions to support staged animations where a transition class is applied with a starting and ending class - Allow JS commands to be executed on DOM nodes outside of the LiveView container ### [](changelog.html#optimization)Optimization - Avoid duplicate statics inside comprehension. In previous versions, comprehensions were able to avoid duplication only in the content of their root. Now we recursively traverse all comprehension nodes and send the static only once for the whole comprehension. This should massively reduce the cost of sending comprehensions over the wire ### [](changelog.html#bug-fixes-59)Bug fixes - Fix HTML engine bug causing expressions to be duplicated or not rendered correctly - Fix HTML engine bug causing slots to not be re-rendered when they should have - Fix form recovery being sent to wrong target ## [](changelog.html#0-17-2-2021-10-22)0.17.2 (2021-10-22) ### [](changelog.html#bug-fixes-60)Bug fixes - Fix HTML engine bug causing attribute expressions to be incorrectly evaluated in certain cases - Fix show/hide/toggle custom display not being restored - Fix default `to` target for `JS.show|hide|dispatch` - Fix form input targeting ## [](changelog.html#0-17-1-2021-10-21)0.17.1 (2021-10-21) ### [](changelog.html#bug-fixes-61)Bug fixes - Fix SVG element support for `phx` binding interactions ## [](changelog.html#0-17-0-2021-10-21)0.17.0 (2021-10-21) ### [](changelog.html#breaking-changes)Breaking Changes #### `on_mount` changes The hook API introduced in LiveView 0.16 has been improved based on feedback. LiveView 0.17 removes the custom module-function callbacks for the [`Phoenix.LiveView.on_mount/1`](Phoenix.LiveView.html#on_mount/1) macro and the `:on_mount` option for [`Phoenix.LiveView.Router.live_session/3`](Phoenix.LiveView.Router.html#live_session/3) in favor of supporting a custom argument. For clarity, the module function to be invoked during the mount lifecycle stage will always be named `on_mount/4`. For example, if you had invoked `on_mount/1` like so: ``` on_mount MyAppWeb.MyHook on_mount {MyAppWeb.MyHook, :assign_current_user} ``` and defined your callbacks as: ``` # my_hook.ex def mount(_params, _session, _socket) do end def assign_current_user(_params, _session, _socket) do end ``` Change the callback to: ``` # my_hook.ex def on_mount(:default, _params, _session, _socket) do end def on_mount(:assign_current_user, _params, _session, _socket) do end ``` When given only a module name, the first argument to `on_mount/4` will be the atom `:default`. #### LEEx templates in stateful LiveComponents Stateful LiveComponents (where an `:id` is given) must now return HEEx templates (`~H` sigil or `.heex` extension). LEEx templates (`~L` sigil or `.leex` extension) are no longer supported. This addresses bugs and allows stateful components to be rendered more efficiently client-side. #### `phx-disconnected` class has been replaced with `phx-loading` Due to a bug in the newly released Safari 15, the previously used `.phx-disconnected` class has been replaced by a new `.phx-loading` class. The reason for the change is `phx.new` included a `.phx-disconnected` rule in the generated `app.css` which triggers the Safari bug. Renaming the class avoids applying the erroneous rule for existing applications. Folks can upgrade by simply renaming their `.phx-disconnected` rules to `.phx-loading`. #### `phx-capture-click` has been deprecated in favor of `phx-click-away` The new `phx-click-away` binding replaces `phx-capture-click` and is much more versatile because it can detect "click focus" being lost on containers. #### Removal of previously deprecated functionality Some functionality that was previously deprecated has been removed: - Implicit assigns in `live_component` do-blocks is no longer supported - Passing a `@socket` to `live_component` will now raise if possible ### [](changelog.html#enhancements-40)Enhancements - Allow slots in function components: they are marked as `<:slot_name>` and can be rendered with `<%= render_slot @slot_name %>` - Add `JS` command for executing JavaScript utility operations on the client with an extended push API - Optimize string attributes: - If the attribute is a string interpolation, such as `<div class={"foo bar #{@baz}"}>`, only the interpolation part is marked as dynamic - If the attribute can be empty, such as "class" and "style", keep the attribute name as static - Add a function component for rendering [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html). Instead of `<%= live_component FormComponent, id: "form" %>`, you must now do: `<.live_component module={FormComponent} id="form" />` ### [](changelog.html#bug-fixes-62)Bug fixes - Fix LiveViews with form recovery failing to properly mount following a reconnect when preceded by a live redirect - Fix stale session causing full redirect fallback when issuing a `push_redirect` from mount - Add workaround for Safari bug causing `<img>` tags with srcset and video with autoplay to fail to render - Support EEx interpolation inside HTML comments in HEEx templates - Support HTML tags inside script tags (as in regular HTML) - Raise if using quotes in attribute names - Include the filename in error messages when it is not possible to parse interpolated attributes - Make sure the test client always sends the full URL on `live_patch`/`live_redirect`. This mirrors the behaviour of the JavaScript client - Do not reload flash from session on `live_redirect`s - Fix select drop-down flashes in Chrome when the DOM is patched during focus ### [](changelog.html#deprecations-9)Deprecations - `<%= live_component MyModule, id: @user.id, user: @user %>` is deprecated in favor of `<.live_component module={MyModule} id={@user.id} user={@user} />`. Notice the new API requires using HEEx templates. This change allows us to further improve LiveComponent and bring new features such as slots to them. - `render_block/2` in deprecated in favor of `render_slot/2` ## [](changelog.html#0-16-4-2021-09-22)0.16.4 (2021-09-22) ### [](changelog.html#enhancements-41)Enhancements - Improve HEEx error messages - Relax HTML tag validation to support mixed case tags - Support self closing HTML tags - Remove requirement for `handle_params` to be defined for lifecycle hooks ### [](changelog.html#bug-fixes-63)Bug fixes - Fix pushes failing to include channel `join_ref` on messages ## [](changelog.html#0-16-3-2021-09-03)0.16.3 (2021-09-03) ### [](changelog.html#bug-fixes-64)Bug fixes - Fix `on_mount` hooks calling view mount before redirecting when the hook issues a halt redirect. ## [](changelog.html#0-16-2-2021-09-03)0.16.2 (2021-09-03) ### [](changelog.html#enhancements-42)Enhancements - Improve error messages on tokenization - Improve error message if `@inner_block` is missing ### [](changelog.html#bug-fixes-65)Bug fixes - Fix `phx-change` form recovery event being sent to wrong component on reconnect when component order changes ## [](changelog.html#0-16-1-2021-08-26)0.16.1 (2021-08-26) ### [](changelog.html#enhancements-43)Enhancements - Relax `phoenix_html` dependency requirement - Allow testing functional components by passing a function reference to [`Phoenix.LiveViewTest.render_component/3`](Phoenix.LiveViewTest.html#render_component/3) ### [](changelog.html#bug-fixes-66)Bug fixes - Do not generate CSRF tokens for non-POST forms - Do not add compile-time dependencies on `on_mount` declarations ## [](changelog.html#0-16-0-2021-08-10)0.16.0 (2021-08-10) ### [](changelog.html#security-considerations-upgrading-from-0-15)Security Considerations Upgrading from 0.15 LiveView v0.16 optimizes live redirects by supporting navigation purely over the existing WebSocket connection. This is accomplished by the new `live_session/3` feature of [`Phoenix.LiveView.Router`](Phoenix.LiveView.Router.html). The [security guide](security-model.html) has always stressed the following: > ... As we have seen, LiveView begins its life-cycle as a regular HTTP request. Then a stateful connection is established. Both the HTTP request and the stateful connection receives the client data via parameters and session. This means that any session validation must happen both in the HTTP request (plug pipeline) and the stateful connection (LiveView mount) ... These guidelines continue to be valid, but it is now essential that the stateful connection enforces authentication and session validation within the LiveView mount lifecycle because **a `live_redirect` from the client will not go through the plug pipeline** as a hard-refresh or initial HTTP render would. This means authentication, authorization, etc that may be done in the [`Plug.Conn`](../plug/1.16.1/Plug.Conn.html) pipeline must also be performed within the LiveView mount lifecycle. Live sessions allow you to support a shared security model by allowing `live_redirect`s to only be issued between routes defined under the same live session name. If a client attempts to live redirect to a different live session, it will be refused and a graceful client-side redirect will trigger a regular HTTP request to the attempted URL. See the [`Phoenix.LiveView.Router.live_session/3`](Phoenix.LiveView.Router.html#live_session/3) docs for more information and example usage. ### [](changelog.html#new-html-engine)New HTML Engine LiveView v0.16 introduces HEEx (HTML + EEx) templates and the concept of function components via [`Phoenix.Component`](Phoenix.Component.html). The new HEEx templates validate the markup in the template while also providing smarter change tracking as well as syntax conveniences to make it easier to build composable components. A function component is any function that receives a map of assigns and returns a `~H` template: ``` defmodule MyComponent do use Phoenix.Component def btn(assigns) do ~H""" <button class="btn"><%= @text %></button> """ end end ``` This component can now be used as in your HEEx templates as: ``` <MyComponent.btn text="Save"> ``` The introduction of HEEx and function components brings a series of deprecation warnings, some introduced in this release and others which will be added in the future. Note HEEx templates require Elixir v1.12+. ### [](changelog.html#upgrading-and-deprecations)Upgrading and deprecations The main deprecation in this release is that the `~L` sigil and the `.leex` extension are now soft-deprecated. The docs have been updated to discourage them and using them will emit warnings in future releases. We recommend using the `~H` sigil and the `.heex` extension for all future templates in your application. You should also plan to migrate the old templates accordingly using the recommendations below. Migrating from `LEEx` to `HEEx` is relatively straightforward. There are two main differences. First of all, HEEx does not allow interpolation inside tags. So instead of: ``` <div id="<%= @id %>"> ... </div> ``` One should use the HEEx syntax: ``` <div id={@id}> ... </div> ``` The other difference is in regards to `form_for`. Some templates may do the following: ``` ~L""" <%= f = form_for @changeset, "#" %> <%= input f, :foo %> </form> """ ``` However, when converted to `~H`, it is not valid HTML: there is a `</form>` tag but its opening is hidden inside the Elixir code. On LiveView v0.16, there is a function component named `form`: ``` ~H""" <.form :let={f} for={@changeset}> <%= input f, :foo %> </.form> """ ``` We understand migrating all templates from `~L` to `~H` can be a daunting task. Therefore we plan to support `~L` in LiveViews for a long time. However, we can't do the same for stateful LiveComponents, as some important client-side features and optimizations will depend on the `~H` sigil. Therefore **our recommendation is to replace `~L` by `~H` first in live components**, particularly stateful live components. Furthermore, stateless `live_component` (i.e. live components without an `:id`) will be deprecated in favor of the new function components. Our plan is to support them for a reasonable period of time, but you should avoid creating new ones in your application. ### [](changelog.html#breaking-changes-1)Breaking Changes LiveView 0.16 removes the `:layout` and `:container` options from `Phoenix.LiveView.Routing.live/4` in favor of the `:root_layout` and `:container` options on `Phoenix.Router.live_session/3`. For instance, if you have the following in LiveView 0.15 and prior: ``` live "/path", MyAppWeb.PageLive, layout: {MyAppWeb.LayoutView, "custom_layout.html"} ``` Change it to: ``` live_session :session_name, root_layout: {MyAppWeb.LayoutView, "custom_layout.html"} do live "/path", MyAppWeb.PageLive end ``` On the client, the `phoenix_live_view` package no longer provides a default export for `LiveSocket`. If you have the following in your JavaScript entrypoint (typically located at `assets/js/app.js`): ``` import LiveSocket from "phoenix_live_view" ``` Change it to: ``` import { LiveSocket } from "phoenix_live_view" ``` Additionally on the client, the root LiveView element no longer exposes the LiveView module name, therefore the `phx-view` attribute is never set. Similarly, the `viewName` property of client hooks has been removed. Codebases calling a custom function `component/3` should rename it or specify its module to avoid a conflict, as LiveView introduces a macro with that name and it is special cased by the underlying engine. ### [](changelog.html#enhancements-44)Enhancements - Introduce HEEx templates - Introduce [`Phoenix.Component`](Phoenix.Component.html) - Introduce `Phoenix.Router.live_session/3` for optimized live redirects - Introduce `on_mount` and `attach_hook` hooks which provide a mechanism to tap into key stages of the LiveView lifecycle - Add upload methods to client-side hooks - \[Helpers] Optimize `live_img_preview` rendering - \[Helpers] Introduce `form` function component which wraps `Phoenix.HTML.form_for` - \[LiveViewTest] Add `with_target` for scoping components directly - \[LiveViewTest] Add `refute_redirected` - \[LiveViewTest] Support multiple `phx-target` values to mirror JS client - \[LiveViewTest] Add `follow_trigger_action` - \[JavaScript Client] Add `sessionStorage` option `LiveSocket` constructor to support client storage overrides - \[JavaScript Client] Do not failsafe reload the page in the background when a tab is unable to connect if the page is not visible ### [](changelog.html#bug-fixes-67)Bug fixes - Make sure components are loaded on `render_component` to ensure all relevant callbacks are invoked - Fix `Phoenix.LiveViewTest.page_title` returning `nil` in some cases - Fix buttons being re-enabled when explicitly set to disabled on server - Fix live patch failing to update URL when live patch link is patched again via `handle_params` within the same callback lifecycle - Fix `phx-no-feedback` class not applied when page is live-patched - Fix `DOMException, querySelector, not a valid selector` when performing DOM lookups on non-standard IDs - Fix select dropdown flashing close/opened when assigns are updated on Chrome/macOS - Fix error with multiple `live_file_input` in one form - Fix race condition in `showError` causing null `querySelector` - Fix statics not resolving correctly across recursive diffs - Fix no function clause matching in `Phoenix.LiveView.Diff.many_to_iodata` - Fix upload input not being cleared after files are uploaded via a component - Fix channel crash when uploading during reconnect - Fix duplicate progress events being sent for large uploads ### [](changelog.html#deprecations-10)Deprecations - Implicit assigns when passing a `do-end` block to `live_component` is deprecated - The `~L` sigil and the `.leex` extension are now soft-deprecated in favor of `~H` and `.heex` - Stateless live components (a `live_component` call without an `:id`) are deprecated in favor of the new function component feature ## [](changelog.html#0-15-7-2021-05-24)0.15.7 (2021-05-24) ### [](changelog.html#bug-fixes-68)Bug fixes - Fix broken webpack build throwing missing morphdom dependency ## [](changelog.html#0-15-6-2021-05-24)0.15.6 (2021-05-24) ### [](changelog.html#bug-fixes-69)Bug fixes - Fix live patch failing to update URL when live patch link is patched again from `handle_params` - Fix regression in `LiveViewTest.render_upload/3` when using channel uploads and progress callback - Fix component uploads not being cleaned up on remove - Fix [`KeyError`](../elixir/KeyError.html) on LiveView reconnect when an active upload was previously in progress ### [](changelog.html#enhancements-45)Enhancements - Support function components via `component/3` - Optimize progress events to send less messages for larger file sizes - Allow session and local storage client overrides ### [](changelog.html#deprecations-11)Deprecations - Deprecate `@socket/socket` argument on `live_component/3` call ## [](changelog.html#0-15-5-2021-04-20)0.15.5 (2021-04-20) ### [](changelog.html#enhancements-46)Enhancements - Add `upload_errors/1` for returning top-level upload errors ### [](changelog.html#bug-fixes-70)Bug fixes - Fix `consume_uploaded_entry/3` with external uploads causing inconsistent entries state - Fix `push_event` losing events when a single diff produces multiple events from different components - Fix deep merging of component tree sharing ## [](changelog.html#0-15-4-2021-01-26)0.15.4 (2021-01-26) ### [](changelog.html#bug-fixes-71)Bug fixes - Fix nested `live_render`'s causing remound of child LiveView even when ID does not change - Do not attempt push hook events unless connected - Fix preflighted refs causing `auto_upload: true` to fail to submit form - Replace single upload entry when `max_entries` is 1 instead of accumulating multiple file selections - Fix `static_path` in `open_browser` failing to load stylesheets ## [](changelog.html#0-15-3-2021-01-02)0.15.3 (2021-01-02) ### [](changelog.html#bug-fixes-72)Bug fixes - Fix `push_redirect` back causing timeout on the client ## [](changelog.html#0-15-2-2021-01-01)0.15.2 (2021-01-01) ### [](changelog.html#backwards-incompatible-changes-5)Backwards incompatible changes - Remove `beforeDestroy` from `phx-hook` callbacks ### [](changelog.html#bug-fixes-73)Bug fixes - Fix form recovery failing to send input on first connection failure - Fix hooks not getting remounted after LiveView reconnect - Fix hooks `reconnected` callback being fired with no prior disconnect ## [](changelog.html#0-15-1-2020-12-20)0.15.1 (2020-12-20) ### [](changelog.html#enhancements-47)Enhancements - Ensure all click events bubble for mobile Safari - Run `consume_uploaded_entries` in LiveView caller process ### [](changelog.html#bug-fixes-74)Bug fixes - Fix hooks not getting remounted after LiveView recovery - Fix bug causing reload with jitter on timeout from previously closed channel - Fix component child nodes being lost when component patch goes from single root node to multiple child siblings - Fix `phx-capture-click` triggering on mouseup during text selection - Fix LiveView `push_event`'s not clearing up in components - Fix `<textarea>` being patched by LiveView while focused ## [](changelog.html#0-15-0-2020-11-20)0.15.0 (2020-11-20) ### [](changelog.html#enhancements-48)Enhancements - Add live uploads support for file progress, interactive file selection, and direct to cloud support - Implement [`Phoenix.LiveViewTest.open_browser/2`](Phoenix.LiveViewTest.html#open_browser/2) that opens up a browser with the LiveView page ### [](changelog.html#backwards-incompatible-changes-6)Backwards incompatible changes - Remove `@inner_content` in components and introduce `render_block` for rendering component `@inner_block` - Remove `@live_module` in socket templates in favor of `@socket.view` ### [](changelog.html#bug-fixes-75)Bug fixes - Make sure URLs are decoded after they are split - Do not recover forms without inputs - Fix race condition when components are removed and then immediately re-added before the client can notify their CIDs have been destroyed - Do not render LiveView if only events/replies have been added to the socket - Properly merge different components when sharing component subtrees on initial render - Allow variables inside do-blocks to be tainted - Fix `push_redirect` from mount hanging on the client and causing a fallback to full page reload when following a clicked `live_redirect` on the client ## [](changelog.html#0-14-8-2020-10-30)0.14.8 (2020-10-30) ### [](changelog.html#bug-fixes-76)Bug fixes - Fix compatibility with latest Plug ## [](changelog.html#0-14-7-2020-09-25)0.14.7 (2020-09-25) ### [](changelog.html#bug-fixes-77)Bug fixes - Fix `redirect(socket, external: ...)` when returned from an event - Properly follow location hashes on live patch/redirect - Fix failure in [`Phoenix.LiveViewTest`](Phoenix.LiveViewTest.html) when `phx-update` has non-HTML nodes as children - Fix `phx_trigger_action` submitting the form before the DOM updates are complete ## [](changelog.html#0-14-6-2020-09-21)0.14.6 (2020-09-21) ### [](changelog.html#bug-fixes-78)Bug fixes - Fix race condition on `phx-trigger-action` causing reconnects before server form submit ## [](changelog.html#0-14-5-2020-09-20)0.14.5 (2020-09-20) ### [](changelog.html#enhancements-49)Enhancements - Optimize DOM prepend and append operations - Add [`Phoenix.LiveView.send_update_after/3`](Phoenix.LiveView.html#send_update_after/3) ### [](changelog.html#bug-fixes-79)Bug fixes - Fix scroll position when using back/forward with `live_redirect`'s - Handle recursive components when generating diffs - Support hard redirects on mount - Properly track nested components on deletion on [`Phoenix.LiveViewTest`](Phoenix.LiveViewTest.html) ## [](changelog.html#0-14-4-2020-07-30)0.14.4 (2020-07-30) ### [](changelog.html#bug-fixes-80)Bug fixes - Fix hidden inputs throwing selection range error ## [](changelog.html#0-14-3-2020-07-24)0.14.3 (2020-07-24) ### [](changelog.html#enhancements-50)Enhancements - Support `render_layout` with LiveEEx ### [](changelog.html#bug-fixes-81)Bug fixes - Fix focused inputs being overwritten by latent patch - Fix LiveView error when `"_target"` input name contains array - Fix change tracking when passing a do-block to components ## [](changelog.html#0-14-2-2020-07-21)0.14.2 (2020-07-21) ### [](changelog.html#bug-fixes-82)Bug fixes - Fix Map of assigns together with `@inner_content` causing `no function clause matching in Keyword.put/3` error - Fix `LiveViewTest` failing to patch children properly for append/prepend based phx-update's - Fix argument error when providing `:as` option to a `live` route - Fix page becoming unresponsive when the server crashes while handling a live patch - Fix empty diff causing pending data-ref based updates, such as classes and `phx-disable-with` content to not be updated - Fix bug where throttling keydown events would eat key presses - Fix `<textarea>`'s failing to be disabled on form submit - Fix text node DOM memory leak when using `phx-update` append/prepend ### [](changelog.html#enhancements-51)Enhancements - Allow `:router` to be given to `render_component` - Display file on compile warning for `~L` - Log error on client when using a hook without a DOM ID - Optimize `phx-update` append/prepend based DOM updates ## [](changelog.html#0-14-1-2020-07-09)0.14.1 (2020-07-09) ### [](changelog.html#bug-fixes-83)Bug fixes - Fix nested `live_render`'s failing to be torn down when removed from the DOM in certain cases - Fix LEEx issue for nested conditions failing to be re-evaluated ## [](changelog.html#0-14-0-2020-07-07)0.14.0 (2020-07-07) ### [](changelog.html#bug-fixes-84)Bug fixes - Fix IE11 issue where `document.activeElement` creates a null reference - Fix setup and teardown of root views when explicitly calling `liveSocket.disconnect()` followed by `liveSocket.connect()` - Fix `error_tag` failing to be displayed for non-text based inputs such as selects and checkboxes as the `phx-no-feedback` class was always applied - Fix `phx-error` class being applied on `live_redirect` - Properly handle Elixir's special variables, such as `__MODULE__` - No longer set disconnected class during patch - Track flash keys to fix back-to-back flashes from being discarded - Properly handle empty component diffs in the client for cases where the component has already been removed on the server - Make sure components in nested live views do not conflict - Fix `phx-static` not being sent from the client for child views - Do not fail when trying to delete a view that was already deleted - Ensure `beforeDestroy` is called on hooks in children of a removed element ### [](changelog.html#enhancements-52)Enhancements - Allow the whole component static subtree to be shared when the component already exists on the client - Add telemetry events to `mount`, `handle_params`, and `handle_event` - Add `push_event` for pushing events and data from the server to the client - Add client `handleEvent` hook method for receiving events pushed from the server - Add ability to receive a reply to a `pushEvent` from the server via `{:reply, map, socket}` - Use event listener for popstate to avoid conflicting with user-defined popstate handlers - Log error on client when rendering a component with no direct DOM children - Make `assigns.myself` a struct to catch mistakes - Log if component doesn't exist on `send_update`, raise if module is unavailable ## [](changelog.html#0-13-3-2020-06-04)0.13.3 (2020-06-04) ### [](changelog.html#bug-fixes-85)Bug fixes - Fix duplicate debounced events from being triggered on blur with timed debounce - Fix client error when `live_redirect`ed route results in a redirect to a non-live route on the server - Fix DOM siblings being removed when a rootless component is updated - Fix debounced input failing to send last change when blurred via Tab, Meta, or other non-printable keys ### [](changelog.html#enhancements-53)Enhancements - Add `dom` option to `LiveSocket` with `onBeforeElUpdated` callback for external client library support of broad DOM operations ## [](changelog.html#0-13-2-2020-05-27)0.13.2 (2020-05-27) ### [](changelog.html#bug-fixes-86)Bug fixes - Fix a bug where swapping a root template with components would cause the LiveView to crash ## [](changelog.html#0-13-1-2020-05-26)0.13.1 (2020-05-26) ### [](changelog.html#bug-fixes-87)Bug fixes - Fix forced page refresh when `push_redirect` from a `live_redirect` ### [](changelog.html#enhancements-54)Enhancements - Optimize component diffs to avoid sending empty diffs - Optimize components to share static values - \[LiveViewTest] Automatically synchronize before render events ## [](changelog.html#0-13-0-2020-05-21)0.13.0 (2020-05-21) ### [](changelog.html#backwards-incompatible-changes-7)Backwards incompatible changes - No longer send event metadata by default. Metadata is now opt-in and user defined at the `LiveSocket` level. To maintain backwards compatibility with pre-0.13 behaviour, you can provide the following metadata option: ``` let liveSocket = new LiveSocket("/live", Socket, { params: {_csrf_token: csrfToken}, metadata: { click: (e, el) => { return { altKey: e.altKey, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, x: e.x || e.clientX, y: e.y || e.clientY, pageX: e.pageX, pageY: e.pageY, screenX: e.screenX, screenY: e.screenY, offsetX: e.offsetX, offsetY: e.offsetY, detail: e.detail || 1, } }, keydown: (e, el) => { return { altGraphKey: e.altGraphKey, altKey: e.altKey, code: e.code, ctrlKey: e.ctrlKey, key: e.key, keyIdentifier: e.keyIdentifier, keyLocation: e.keyLocation, location: e.location, metaKey: e.metaKey, repeat: e.repeat, shiftKey: e.shiftKey } } } }) ``` ### [](changelog.html#bug-fixes-88)Bug fixes - Fix error caused by Chrome sending a keydown event on native UI autocomplete without a `key` - Fix server error when a live navigation request issues a redirect - Fix double window bindings when explicit calls to LiveSocket connect/disconnect/connect ### [](changelog.html#enhancements-55)Enhancements - Add `Phoenix.LiveView.get_connect_info/1` - Add `Phoenix.LiveViewTest.put_connect_info/2` and [`Phoenix.LiveViewTest.put_connect_params/2`](Phoenix.LiveViewTest.html#put_connect_params/2) - Add support for tracking static asset changes on the page across cold server deploys - Add support for passing a `@myself` target to a hook's `pushEventTo` target - Add configurable metadata for events with new `metadata` LiveSocket option - Add `"_mounts"` key in connect params which specifies the number of times a LiveView has mounted ## [](changelog.html#0-12-1-2020-04-19)0.12.1 (2020-04-19) ### [](changelog.html#bug-fixes-89)Bug fixes - Fix component `innerHTML` being discarded when a sibling DOM element appears above it, in cases where the component lacks a DOM ID - Fix Firefox reconnecting briefly during hard redirects - Fix `phx-disable-with` and other pending attributes failing to be restored when an empty patch is returned by server - Ensure LiveView module is loaded before mount to prevent first application request logging errors if the very first request is to a connected LiveView ## [](changelog.html#0-12-0-2020-04-16)0.12.0 (2020-04-16) This version of LiveView comes with an overhaul of the testing module, more closely integrating your LiveView template with your LiveView events. For example, in previous versions, you could write this test: ``` render_click(live_view, "increment_by", %{by: 1}) ``` However, there is no guarantee that there is any element on the page with a `phx-click="increment"` attribute and `phx-value-by` set to 1. With LiveView 0.12.0, you can now write: ``` live_view |> element("#term .buttons a", "Increment") |> render_click() ``` The new implementation will check there is a button at `#term .buttons a`, with "Increment" as text, validate that it has a `phx-click` attribute and automatically submit to it with all relevant `phx-value` entries. This brings us closer to integration/acceptance test frameworks without any of the overhead and complexities of running a headless browser. ### [](changelog.html#enhancements-56)Enhancements - Add `assert_patch/3` and `assert_patched/2` for asserting on patches - Add `follow_redirect/3` to automatically follow redirects from `render_*` events - Add `phx-trigger-action` form annotation to trigger an HTTP form submit on next DOM patch ### [](changelog.html#bug-fixes-90)Bug fixes - Fix `phx-target` `@myself` targeting a sibling LiveView component with the same component ID - Fix `phx:page-loading-stop` firing before the DOM patch has been performed - Fix `phx-update="prepend"` failing to properly patch the DOM when the same ID is updated back to back - Fix redirects on mount failing to copy flash ### [](changelog.html#backwards-incompatible-changes-8)Backwards incompatible changes - `phx-error-for` has been removed in favor of `phx-feedback-for`. `phx-feedback-for` will set a `phx-no-feedback` class whenever feedback has to be hidden - `Phoenix.LiveViewTest.children/1` has been renamed to [`Phoenix.LiveViewTest.live_children/1`](Phoenix.LiveViewTest.html#live_children/1) - `Phoenix.LiveViewTest.find_child/2` has been renamed to [`Phoenix.LiveViewTest.find_live_child/2`](Phoenix.LiveViewTest.html#find_live_child/2) - [`Phoenix.LiveViewTest.assert_redirect/3`](Phoenix.LiveViewTest.html#assert_redirect/3) no longer matches on the flash, instead it returns the flash - [`Phoenix.LiveViewTest.assert_redirect/3`](Phoenix.LiveViewTest.html#assert_redirect/3) no longer matches on the patch redirects, use `assert_patch/3` instead - `Phoenix.LiveViewTest.assert_remove/3` has been removed. If the LiveView crashes, it will cause the test to crash too - Passing a path with DOM IDs to `render_*` test functions is deprecated. Furthermore, they now require a `phx-target="<%= @id %>"` on the given DOM ID: ``` <div id="component-id" phx-target="component-id"> ... </div> ``` ``` html = render_submit([view, "#component-id"], event, value) ``` In any case, this API is deprecated and you should migrate to the new element based API. ## [](changelog.html#0-11-1-2020-04-08)0.11.1 (2020-04-08) ### [](changelog.html#bug-fixes-91)Bug fixes - Fix readonly states failing to be undone after an empty diff - Fix dynamically added child failing to be joined by the client - Fix teardown bug causing stale client sessions to attempt a rejoin on reconnect - Fix orphaned prepend/append content across joins - Track `unless` in LiveEEx engine ### [](changelog.html#backwards-incompatible-changes-9)Backwards incompatible changes - `render_event`/`render_click` and friends now expect a DOM ID selector to be given when working with components. For example, instead of `render_click([live, "user-13"])`, you should write `render_click([live, "#user-13"])`, mirroring the `phx-target` API. - Accessing the socket assigns directly `@socket.assigns[...]` in a template will now raise the exception `Phoenix.LiveView.Socket.AssignsNotInSocket`. The socket assigns are available directly inside the template as LiveEEx `assigns`, such as `@foo` and `@bar`. Any assign access should be done using the assigns in the template where proper change tracking takes place. ### [](changelog.html#enhancements-57)Enhancements - Trigger debounced events immediately on input blur - Support `defaults` option on `LiveSocket` constructor to configure default `phx-debounce` and `phx-throttle` values, allowing `<input ... phx-debounce>` - Add `detail` key to click event metadata for detecting double/triple clicks ## [](changelog.html#0-11-0-2020-04-06)0.11.0 (2020-04-06) ### [](changelog.html#backwards-incompatible-changes-10)Backwards incompatible changes - Remove `socket.assigns` during render to avoid change tracking bugs. If you were previously relying on passing `@socket` to functions then referencing socket assigns, pass the explicit assign instead to your functions from the template. - Removed `assets/css/live_view.css`. If you want to show a progress bar then in `app.css`, replace ``` - @import "../../../../deps/phoenix_live_view/assets/css/live_view.css"; + @import "../node_modules/nprogress/nprogress.css"; ``` and add `nprogress` to `assets/package.json`. Full details in the [Progress animation guide](0.11.0/installation.html#progress-animation) ### [](changelog.html#bug-fixes-92)Bug fixes - Fix client issue with greater than two levels of LiveView nesting - Fix bug causing entire LiveView to be re-rendering with only a component changed - Fix issue where rejoins would not trigger `phx:page-loading-stop` ### [](changelog.html#enhancements-58)Enhancements - Support deep change tracking so `@foo.bar` only executes and diffs when bar changes - Add `@myself` assign, to allow components to target themselves instead of relying on a DOM ID, for example: `phx-target="<%= @myself %>"` - Optimize various client rendering scenarios for faster DOM patching of components and append/prepended content - Add `enableProfiling()` and `disableProfiling()` to `LiveSocket` for client performance profiling to aid the development process - Allow LiveViews to be rendered inside LiveComponents - Add support for clearing flash inside components ## [](changelog.html#0-10-0-2020-03-18)0.10.0 (2020-03-18) ### [](changelog.html#backwards-incompatible-changes-11)Backwards incompatible changes - Rename socket assign `@live_view_module` to `@live_module` - Rename socket assign `@live_view_action` to `@live_action` - LiveView no longer uses the default app layout and `put_live_layout` is no longer supported. Instead, use `put_root_layout`. Note, however, that the layout given to `put_root_layout` must use `@inner_content` instead of `<%= render(@view_module, @view_template, assigns) %>` and that the root layout will also be used by regular views. Check the [Live Layouts](0.10.0/Phoenix.LiveView.html#module-live-layouts) section of the docs. ### [](changelog.html#bug-fixes-93)Bug fixes - Fix loading states causing nested LiveViews to be removed during live navigation - Only trigger `phx-update="ignore"` hook if data attributes have changed - Fix LiveEEx fingerprint bug causing no diff to be sent in certain cases ### [](changelog.html#enhancements-59)Enhancements - Support collocated templates where an `.html.leex` template of the same basename of the LiveView will be automatically used for `render/1` - Add `live_title_tag/2` helper for automatic prefix/suffix on `@page_title` updates ## [](changelog.html#0-9-0-2020-03-08)0.9.0 (2020-03-08) ### [](changelog.html#bug-fixes-94)Bug fixes - Do not set ignored inputs and buttons as readonly - Only decode paths in URIs - Only destroy main descendents when replacing main - Fix sibling component patches when siblings at same root DOM tree - Do not pick the layout from `:use` on child LiveViews - Respect when the layout is set to `false` in the router and on mount - Fix sibling component patch when component siblings lack a container - Make flash optional (i.e. LiveView will still work if you don't `fetch_flash` before) ### [](changelog.html#enhancements-60)Enhancements - Raise if `:flash` is given as an assign - Support user-defined metadata in router - Allow the router to be accessed as `socket.router` - Allow `MFArgs` as the `:session` option in the `live` router macro - Trigger page loading event when main LV errors - Automatically clear the flash on live navigation examples - only the newly assigned flash is persisted ## [](changelog.html#0-8-1-2020-02-27)0.8.1 (2020-02-27) ### [](changelog.html#enhancements-61)Enhancements - Support `phx-disable-with` on live redirect and live patch links ### [](changelog.html#bug-fixes-95)Bug Fixes - Fix focus issue on date and time inputs - Fix LiveViews failing to mount when page restored from back/forward cache following a `redirect` on the server - Fix IE coercing `undefined` to string when issuing pushState - Fix IE error when focused element is null - Fix client error when using components and live navigation where a dynamic template is rendered - Fix latent component diff causing error when component removed from DOM before patch arrives - Fix race condition where a component event received on the server for a component already removed by the server raised a match error ## [](changelog.html#0-8-0-2020-02-22)0.8.0 (2020-02-22) ### [](changelog.html#backwards-incompatible-changes-12)Backwards incompatible changes - Remove `Phoenix.LiveView.Flash` in favor of `:fetch_live_flash` imported by [`Phoenix.LiveView.Router`](Phoenix.LiveView.Router.html) - Live layout must now access the child contents with `@inner_content` instead of invoking the LiveView directly - Returning `:stop` tuples from LiveView `mount` or `handle_[params|call|cast|info|event]` is no longer supported. LiveViews are stopped when issuing a `redirect` or `push_redirect` ### [](changelog.html#enhancements-62)Enhancements - Add `put_live_layout` plug to put the root layout used for live routes - Allow `redirect` and `push_redirect` from mount - Use acknowledgement tracking to avoid patching inputs until the server has processed the form event - Add CSS loading states to all phx bound elements with event specific CSS classes - Dispatch `phx:page-loading-start` and `phx:page-loading-stop` on window for live navigation, initial page loads, and form submits, for user controlled page loading integration - Allow any phx bound element to specify `phx-page-loading` to dispatch loading events above when the event is pushed - Add client side latency simulator with new `enableLatencySim(milliseconds)` and `disableLatencySim()` - Add `enableDebug()` and `disableDebug()` to `LiveSocket` for ondemand browser debugging from the web console - Do not connect LiveSocket WebSocket or bind client events unless a LiveView is found on the page - Add `transport_pid/1` to return the websocket transport pid when the socket is connected ### [](changelog.html#bug-fixes-96)Bug Fixes - Fix issue where a failed mount from a `live_redirect` would reload the current URL instead of the attempted new URL ## [](changelog.html#0-7-1-2020-02-13)0.7.1 (2020-02-13) ### [](changelog.html#bug-fixes-97)Bug Fixes - Fix checkbox bug failing to send `phx-change` event to the server in certain cases - Fix checkbox bug failing to maintain checked state when a checkbox is programmatically updated by the server - Fix select bug in Firefox causing the highlighted index to jump when a patch is applied during hover state ## [](changelog.html#0-7-0-2020-02-12)0.7.0 (2020-02-12) ### [](changelog.html#backwards-incompatible-changes-13)Backwards incompatible changes - `live_redirect` was removed in favor of `push_patch` (for updating the URL and params of the current LiveView) and `push_redirect` (for updating the URL to another LiveView) - `live_link` was removed in favor of `live_patch` (for updating the URL and params of the current LiveView) and `live_redirect` (for updating the URL to another LiveView) - `Phoenix.LiveViewTest.assert_redirect` no longer accepts an anonymous function in favor of executing the code prior to asserting the redirects, just like `assert_receive`. ### [](changelog.html#enhancements-63)Enhancements - Support `@live_view_action` in LiveViews to simplify tracking of URL state - Recovery form input data automatically on disconnects or crash recovery - Add `phx-auto-recover` form binding for specialized recovery - Scroll to top of page while respecting anchor hash targets on `live_patch` and `live_redirect` - Add `phx-capture-click` to use event capturing to bind a click event as it propagates inwards from the target - Revamp flash support so it works between static views, live views, and components - Add `phx-key` binding to scope `phx-window-keydown` and `phx-window-keyup` events ### [](changelog.html#bug-fixes-98)Bug Fixes - Send `phx-value-*` on key events - Trigger `updated` hook callbacks on `phx-update="ignore"` container when the container's attributes have changed - Fix nested `phx-update="append"` raising ArgumentError in LiveViewTest - Fix updates not being applied in rare cases where an leex template is wrapped in an if expression ## [](changelog.html#0-6-0-2020-01-22)0.6.0 (2020-01-22) ### [](changelog.html#deprecations-12)Deprecations - LiveView `mount/2` has been deprecated in favor of `mount/3`. The params are now passed as the first argument to `mount/3`, followed by the session and socket. ### [](changelog.html#backwards-incompatible-changes-14)Backwards incompatible changes - The socket session now accepts only string keys ### [](changelog.html#enhancements-64)Enhancements - Allow window beforeunload to be cancelled without losing websocket connection ### [](changelog.html#bug-fixes-99)Bug Fixes - Fix handle\_params not decoding URL path parameters properly - Fix LiveViewTest error when routing at root path - Fix URI encoded params failing to be decoded in `handle_params` - Fix component target failing to locate correct component when the target is on an input tag ## [](changelog.html#0-5-2-2020-01-17)0.5.2 (2020-01-17) ### [](changelog.html#bug-fixes-100)Bug Fixes - Fix optimization bug causing some DOM nodes to be removed on updates ## [](changelog.html#0-5-1-2020-01-15)0.5.1 (2020-01-15) ### [](changelog.html#bug-fixes-101)Bug Fixes - Fix phx-change bug causing phx-target to not be used ## [](changelog.html#0-5-0-2020-01-15)0.5.0 (2020-01-15) LiveView now makes the connection session automatically available in LiveViews. However, to do so, you need to configure your endpoint accordingly, **otherwise LiveView will fail to connect**. The steps are: 1. Find `plug Plug.Session, ...` in your endpoint.ex and move the options `...` to a module attribute: ``` @session_options [ ... ] ``` 2. Change the `plug Plug.Session` to use said attribute: ``` plug Plug.Session, @session_options ``` 3. Also pass the `@session_options` to your LiveView socket: ``` socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] ``` 4. You should define the CSRF meta tag inside &lt;head&gt; in your layout, before `app.js` is included: ``` <meta name="csrf-token" content={Plug.CSRFProtection.get_csrf_token()} /> <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script> ``` 5. Then in your app.js: ``` let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}); ``` Also note that **the session from now on will have string keys**. LiveView will warn if atom keys are used. ### [](changelog.html#enhancements-65)Enhancements - Respect new tab behavior in `live_link` - Add `beforeUpdate` and `beforeDestroy` JS hooks - Make all assigns defined on the socket mount available on the layout on first render - Provide support for live layouts with new `:layout` option - Detect duplicate IDs on the front-end when DEBUG mode is enabled - Automatically forward the session to LiveView - Support "live\_socket\_id" session key for identifying (and disconnecting) LiveView sockets - Add support for `hibernate_after` on LiveView processes - Support redirecting to full URLs on `live_redirect` and `redirect` - Add `offsetX` and `offsetY` to click event metadata - Allow `live_link` and `live_redirect` to exist anywhere in the page and it will always target the main LiveView (the one defined at the router) ### [](changelog.html#backwards-incompatible-changes-15)Backwards incompatible changes - `phx-target="window"` has been removed in favor of `phx-window-keydown`, `phx-window-focus`, etc, and the `phx-target` binding has been repurposed for targeting LiveView and LiveComponent events from the client - [`Phoenix.LiveView`](Phoenix.LiveView.html) no longer defined `live_render` and `live_link`. These functions have been moved to `Phoenix.LiveView.Helpers` which can now be fully imported in your views. In other words, replace `import Phoenix.LiveView, only: [live_render: ..., live_link: ...]` by `import Phoenix.LiveView.Helpers` ## [](changelog.html#0-4-1-2019-11-07)0.4.1 (2019-11-07) ### [](changelog.html#bug-fixes-102)Bug Fixes - Fix bug causing blurred inputs ## [](changelog.html#0-4-0-2019-11-07)0.4.0 (2019-11-07) ### [](changelog.html#enhancements-66)Enhancements - Add [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) to compartmentalize state, markup, and events in LiveView - Handle outdated clients by refreshing the page with jitter when a valid, but outdated session is detected - Only dispatch live link clicks to router LiveView - Refresh the page for graceful error recovery on failed mount when the socket is in a connected state ### [](changelog.html#bug-fixes-103)Bug Fixes - Fix `phx-hook` destroyed callback failing to be called in certain cases - Fix back/forward bug causing LiveView to fail to remount ## [](changelog.html#0-3-1-2019-09-23)0.3.1 (2019-09-23) ### [](changelog.html#backwards-incompatible-changes-16)Backwards incompatible changes - `live_isolated` in tests no longer requires a router and a pipeline (it now expects only 3 arguments) - Raise if `handle_params` is used on a non-router LiveView ### [](changelog.html#bug-fixes-104)Bug Fixes - \[LiveViewTest] Fix function clause errors caused by HTML comments ## [](changelog.html#0-3-0-2019-09-19)0.3.0 (2019-09-19) ### [](changelog.html#enhancements-67)Enhancements - Add `phx-debounce` and `phx-throttle` bindings to rate limit events ### [](changelog.html#backwards-incompatible-changes-17)Backwards incompatible changes - IE11 support now requires two additional polyfills, `mdn-polyfills/CustomEvent` and `mdn-polyfills/String.prototype.startsWith` ### [](changelog.html#bug-fixes-105)Bug Fixes - Fix IE11 support caused by unsupported `getAttributeNames` lookup - Fix Floki dependency compilation warnings ## [](changelog.html#0-2-1-2019-09-17)0.2.1 (2019-09-17) ### [](changelog.html#bug-fixes-106)Bug Fixes - \[LiveView.Router] Fix module concat failing to build correct layout module when using custom namespace - \[LiveViewTest] Fix `phx-update` append/prepend containers not building proper DOM content - \[LiveViewTest] Fix `phx-update` append/prepend containers not updating existing child containers with matching IDs ## [](changelog.html#0-2-0-2019-09-12)0.2.0 (2019-09-12) ### [](changelog.html#enhancements-68)Enhancements - \[LiveView] Add new `:container` option to `use Phoenix.LiveView` - \[LiveViewTest] Add `live_isolated` test helper for testing LiveViews which are not routable ### [](changelog.html#backwards-incompatible-changes-18)Backwards incompatible changes - Replace `configure_temporary_assigns/2` with 3-tuple mount return, supporting a `:temporary_assigns` key - Do not allow `redirect`/`live_redirect` on mount nor in child live views - All `phx-update` containers now require a unique ID - `LiveSocket` JavaScript constructor now requires explicit dependency injection of Phoenix Socket constructor. For example: ``` import {Socket} from "phoenix" import LiveSocket from "phoenix_live_view" let liveSocket = new LiveSocket("/live", Socket, {...}) ``` ### [](changelog.html#bug-fixes-107)Bug Fixes - Fix `phx-update=append/prepend` failing to join new nested live views or wire up new phx-hooks - Fix number input handling causing some browsers to reset form fields on invalid inputs - Fix multi-select decoding causing server error - Fix multi-select change tracking failing to submit an event when a value is deselected - Fix live redirect loop triggered under certain scenarios - Fix params failing to update on re-mounts after live\_redirect - Fix blur event metadata being sent with type of `"focus"` ## [](changelog.html#0-1-2-2019-08-28)0.1.2 (2019-08-28) ### [](changelog.html#backwards-incompatible-changes-19)Backwards incompatible changes - `phx-value` has no effect, use `phx-value-*` instead - The `:path_params` key in session has no effect (use `handle_params` in LiveView instead) ## [](changelog.html#0-1-1-2019-08-27)0.1.1 (2019-08-27) ### [](changelog.html#enhancements-69)Enhancements - Use optimized `insertAdjacentHTML` for faster append/prepend and proper CSS animation handling - Allow for replacing previously appended/prepended elements by replacing duplicate IDs during append/prepend instead of adding new DOM nodes ### [](changelog.html#bug-fixes-108)Bug Fixes - Fix duplicate append/prepend updates when parent content is updated - Fix JS hooks not being applied for appending and prepended content ## [](changelog.html#0-1-0-2019-08-25)0.1.0 (2019-08-25) ### [](changelog.html#enhancements-70)Enhancements - The LiveView `handle_in/3` callback now receives a map of metadata about the client event - For `phx-change` events, `handle_in/3` now receives a `"_target"` param representing the keyspace of the form input name which triggered the change - Multiple values may be provided for any phx binding by using the `phx-value-` prefix, such as `phx-value-myval1`, `phx-value-myval2`, etc - Add control over the DOM patching via `phx-update`, which can be set to `"replace"`, `"append"`, `"prepend"` or `"ignore"` ### [](changelog.html#backwards-incompatible-changes-20)Backwards incompatible changes - `phx-ignore` was renamed to `phx-update="ignore"` [← Previous Page API Reference](api-reference.html) [Next Page → Welcome](welcome.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/CHANGELOG.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/deployments.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/deployments.md#L1 "View Source") Deployments and recovery One of the questions that arise from LiveView stateful model is what considerations are necessary when deploying a new version of LiveView (or when recovering from an error). First off, whenever LiveView disconnects, it will automatically attempt to reconnect to the server using exponential back-off. This means it will try immediately, then wait 2s and try again, then 5s and so on. If you are deploying, this typically means the next reconnection will immediately succeed and your load balancer will automatically redirect to the new servers. However, your LiveView *may* still have state that will be lost in this transition. How to deal with it? The good news is that there are a series of practices you can follow that will not only help with deployments but it will improve your application in general. 1. Keep state in the query parameters when appropriate. For example, if your application has tabs and the user clicked a tab, instead of using `phx-click` and [`Phoenix.LiveView.handle_event/3`](Phoenix.LiveView.html#c:handle_event/3) to manage it, you should implement it using `<.link patch={...}>` passing the tab name as parameter. You will then receive the new tab name [`Phoenix.LiveView.handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) which will set the relevant assign to choose which tab to display. You can even define specific URLs for each tab in your application router. By doing this, you will reduce the amount of server state, make tab navigation shareable via links, improving search engine indexing, and more. 2. Consider storing other relevant state in the database. For example, if you are building a chat app and you want to store which messages have been read, you can store so in the database. Once the page is loaded, you retrieve the index of the last read message. This makes the application more robust, allow data to be synchronized across devices, etc. 3. If your application uses forms (which is most likely the case), keep in mind that Phoenix performs automatic form recovery: in case of disconnections, Phoenix will collect the form data and resubmit it on reconnection. This mechanism works out of the box for most forms but you may want to customize it or test it for your most complex forms. See the relevant section [in the "Form bindings" document](form-bindings.html) to learn more. The idea is that: if you follow the practices above, most of your state is already handled within your app and therefore deployments should not bring additional concerns. Not only that, it will bring overall benefits to your app such as indexing, link sharing, device sharing, and so on. If you really have complex state that cannot be immediately handled, then you may need to resort to special strategies. This may be persisting "old" state to Redis/S3/Database and loading the new state on the new connections. Or you may take special care when migrating connections (for example, if you are building a game, you may want to wait for on-going sessions to finish before turning down the old server while routing new sessions to the new ones). Such cases will depend on your requirements (and they would likely exist regardless of which application stack you are using). [← Previous Page Assigns and HEEx templates](assigns-eex.html) [Next Page → Error and exception handling](error-handling.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/deployments.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/error-handling.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/error-handling.md#L1 "View Source") Error and exception handling As with any other Elixir code, exceptions may happen during the LiveView life-cycle. This page describes how LiveView handles errors at different stages. ## [](error-handling.html#expected-scenarios)Expected scenarios In this section, we will talk about error cases that you expect to happen within your application. For example, a user filling in a form with invalid data is expected. In a LiveView, we typically handle those cases by storing the form state in LiveView assigns and rendering any relevant error message back to the client. We may also use `flash` messages for this. For example, imagine you have a page to manage all "Team members" in an organization. However, if there is only one member left in the organization, they should not be allowed to leave. You may want to handle this by using flash messages: ``` if MyApp.Org.leave(socket.assigns.current_org, member) do {:noreply, socket} else {:noreply, put_flash(socket, :error, "last member cannot leave organization")} end ``` However, one may argue that, if the last member of an organization cannot leave it, it may be better to not even show the "Leave" button in the UI when the organization has only one member. Given the button does not appear in the UI, triggering the "leave" action when the organization has only one member is an unexpected scenario. This means we can rewrite the code above to: ``` true = MyApp.Org.leave(socket.assigns.current_org, member) {:noreply, socket} ``` If `leave` does not return `true`, Elixir will raise a [`MatchError`](../elixir/MatchError.html) exception. Or you could provide a `leave!` function that raises a specific exception: ``` MyApp.Org.leave!(socket.assigns.current_org, member) {:noreply, socket} ``` However, what will happen with a LiveView in case of exceptions? Let's talk about unexpected scenarios. ## [](error-handling.html#unexpected-scenarios)Unexpected scenarios Elixir developers tend to write assertive code. This means that, if we expect `leave` to always return true, we can explicitly match on its result, as we did above: ``` true = MyApp.Org.leave(socket.assigns.current_org, member) {:noreply, socket} ``` If `leave` fails and returns `false`, an exception is raised. It is common for Elixir developers to use exceptions for unexpected scenarios in their Phoenix applications. For example, if you are building an application where a user may belong to one or more organizations, when accessing the organization page, you may want to check that the user has access to it like this: ``` organizations_query = Ecto.assoc(socket.assigns.current_user, :organizations) Repo.get!(organizations_query, params["org_id"]) ``` The code above builds a query that returns all organizations that belongs to the current user and then validates that the given `org_id` belongs to the user. If there is no such `org_id` or if the user has no access to it, `Repo.get!` will raise an `Ecto.NoResultsError` exception. During a regular controller request, this exception will be converted to a 404 exception and rendered as a custom error page, as [detailed here](../phoenix/custom_error_pages.html). LiveView will react to exceptions in three different ways, depending on where it is in its life-cycle. ### [](error-handling.html#exceptions-during-http-mount)Exceptions during HTTP mount When you first access a LiveView, a regular HTTP request is sent to the server and processed by the LiveView. The `mount` callback is invoked and then a page is rendered. Any exception here is caught, logged, and converted to an exception page by Phoenix error views - exactly how it works with controllers too. ### [](error-handling.html#exceptions-during-connected-mount)Exceptions during connected mount If the initial HTTP request succeeds, LiveView will connect to the server using a stateful connection, typically a WebSocket. This spawns a long-running lightweight Elixir process on the server, which invokes the `mount` callback and renders an updated version of the page. An exception during this stage will crash the LiveView process, which will be logged. Once the client notices the crash, it fully reloads the page. This will cause `mount` to be invoked again during a regular HTTP request (the exact scenario of the previous subsection). In other words, LiveView will reload the page in case of errors, making it fail as if LiveView was not involved in the rendering in the first place. ### [](error-handling.html#exceptions-after-connected-mount)Exceptions after connected mount Once your LiveView is mounted and connected, any error will cause the LiveView process to crash and be logged. Once the client notices the error, it will remount the LiveView over the stateful connection, without reloading the page (the exact scenario of the previous subsection). If remounting succeeds, the LiveView goes back to a working state, updating the page and showing the user the latest information. For example, let's say two users try to leave the organization at the same time. In this case, both of them see the "Leave" button, but our `leave` function call will succeed only for one of them: ``` true = MyApp.Org.leave(socket.assigns.current_org, member) {:noreply, socket} ``` When the exception raises, the client will remount the LiveView. Once you remount, your code will now notice that there is only one user in the organization and therefore no longer show the "Leave" button. In other words, by remounting, we often update the state of the page, allowing exceptions to be automatically handled. Note that the choice between conditionally checking on the result of the `leave` function with an `if`, or simply asserting it returns `true`, is completely up to you. If the likelihood of everyone leaving the organization at the same time is low, then you may as well treat it as an unexpected scenario. Although other developers will be more comfortable by explicitly handling those cases. In both scenarios, LiveView has you covered. Finally, if your LiveView crashes, its current state will be lost. Luckily, LiveView has a series of mechanisms and best practices you can follow to ensure the user is shown the same page as before during reconnections. See the ["Deployments and recovery"](deployments.html) guide for more information. [← Previous Page Deployments and recovery](deployments.html) [Next Page → Gettext for internationalization](gettext.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/error-handling.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/external-uploads.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/client/external-uploads.md#L1 "View Source") External uploads > This guide continues from the configuration started in the server [Uploads guide](uploads.html). Uploads to external cloud providers, such as Amazon S3, Google Cloud, etc., can be achieved by using the `:external` option in [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3). You provide a 2-arity function to allow the server to generate metadata for each upload entry, which is passed to a user-specified JavaScript function on the client. Typically when your function is invoked, you will generate a pre-signed URL, specific to your cloud storage provider, that will provide temporary access for the end-user to upload data directly to your cloud storage. ## [](external-uploads.html#chunked-http-uploads)Chunked HTTP Uploads For any service that supports large file uploads via chunked HTTP requests with `Content-Range` headers, you can use the UpChunk JS library by Mux to do all the hard work of uploading the file. For small file uploads or to get started quickly, consider [uploading directly to S3](external-uploads.html#direct-to-s3) instead. You only need to wire the UpChunk instance to the LiveView UploadEntry callbacks, and LiveView will take care of the rest. Install [UpChunk](https://github.com/muxinc/upchunk) by saving [its contents](https://unpkg.com/@mux/upchunk@2) to `assets/vendor/upchunk.js` or by installing it with `npm`: ``` $ npm install --prefix assets --save @mux/upchunk ``` Configure your uploader on [`Phoenix.LiveView.mount/3`](Phoenix.LiveView.html#c:mount/3): ``` def mount(_params, _session, socket) do {:ok, socket |> assign(:uploaded_files, []) |> allow_upload(:avatar, accept: :any, max_entries: 3, external: &presign_upload/2)} end ``` Supply the `:external` option to [`Phoenix.LiveView.allow_upload/3`](Phoenix.LiveView.html#allow_upload/3). It requires a 2-arity function that generates a signed URL where the client will push the bytes for the upload entry. This function must return either `{:ok, meta, socket}` or `{:error, meta, socket}`, where `meta` must be a map. For example, if you were using a context that provided a [`start_session`](https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol#%23Start_Resumable_Session) function, you might write something like this: ``` defp presign_upload(entry, socket) do {:ok, %{"Location" => link}} = SomeTube.start_session(%{ "uploadType" => "resumable", "x-upload-content-length" => entry.client_size }) {:ok, %{uploader: "UpChunk", entrypoint: link}, socket} end ``` Finally, on the client-side, we use UpChunk to create an upload from the temporary URL generated on the server and attach listeners for its events to the entry's callbacks: ``` import * as UpChunk from "@mux/upchunk" let Uploaders = {} Uploaders.UpChunk = function(entries, onViewError){ entries.forEach(entry => { // create the upload session with UpChunk let { file, meta: { entrypoint } } = entry let upload = UpChunk.createUpload({ endpoint: entrypoint, file }) // stop uploading in the event of a view error onViewError(() => upload.pause()) // upload error triggers LiveView error upload.on("error", (e) => entry.error(e.detail.message)) // notify progress events to LiveView upload.on("progress", (e) => { if(e.detail < 100){ entry.progress(e.detail) } }) // success completes the UploadEntry upload.on("success", () => entry.progress(100)) }) } // Don't forget to assign Uploaders to the liveSocket let liveSocket = new LiveSocket("/live", Socket, { uploaders: Uploaders, params: {_csrf_token: csrfToken} }) ``` ## [](external-uploads.html#direct-to-s3)Direct to S3 The largest object that can be uploaded to S3 in a single PUT is 5 GB according to [S3 FAQ](https://aws.amazon.com/s3/faqs/). For larger file uploads, consider using chunking as shown above. This guide assumes an existing S3 bucket is set up with the correct CORS configuration which allows uploading directly to the bucket. An example CORS config is: ``` [ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "PUT", "POST" ], "AllowedOrigins": [ "*" ], "ExposeHeaders": [] } ] ``` You may put your domain in the "allowedOrigins" instead. More information on configuring CORS for S3 buckets is [available on AWS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html). In order to enforce all of your file constraints when uploading to S3, it is necessary to perform a multipart form POST with your file data. You should have the following S3 information ready before proceeding: 1. aws\_access\_key\_id 2. aws\_secret\_access\_key 3. bucket\_name 4. region We will first implement the LiveView portion: ``` def mount(_params, _session, socket) do {:ok, socket |> assign(:uploaded_files, []) |> allow_upload(:avatar, accept: :any, max_entries: 3, external: &presign_upload/2)} end defp presign_upload(entry, socket) do uploads = socket.assigns.uploads bucket = "phx-upload-example" key = "public/#{entry.client_name}" config = %{ region: "us-east-1", access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"), secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY") } {:ok, fields} = SimpleS3Upload.sign_form_upload(config, bucket, key: key, content_type: entry.client_type, max_file_size: uploads[entry.upload_config].max_file_size, expires_in: :timer.hours(1) ) meta = %{uploader: "S3", key: key, url: "http://#{bucket}.s3-#{config.region}.amazonaws.com", fields: fields} {:ok, meta, socket} end ``` Here, we implemented a `presign_upload/2` function, which we passed as a captured anonymous function to `:external`. It generates a pre-signed URL for the upload and returns our `:ok` result, with a payload of metadata for the client, along with our unchanged socket. Next, we add a missing module `SimpleS3Upload` to generate pre-signed URLs for S3. Create a file called `simple_s3_upload.ex`. Get the file's content from this zero-dependency module called [`SimpleS3Upload`](https://gist.github.com/chrismccord/37862f1f8b1f5148644b75d20d1cb073) written by Chris McCord. > Tip: if you encounter errors with the `:crypto` module or with S3 blocking ACLs, please read the comments in the gist above for solutions. Next, we add our JavaScript client-side uploader. The metadata *must* contain the `:uploader` key, specifying the name of the JavaScript client-side uploader. In this case, it's `"S3"`, as shown above. Add a new file `uploaders.js` in the following directory `assets/js/` next to `app.js`. The content for this `S3` client uploader: ``` let Uploaders = {} Uploaders.S3 = function(entries, onViewError){ entries.forEach(entry => { let formData = new FormData() let {url, fields} = entry.meta Object.entries(fields).forEach(([key, val]) => formData.append(key, val)) formData.append("file", entry.file) let xhr = new XMLHttpRequest() onViewError(() => xhr.abort()) xhr.onload = () => xhr.status === 204 ? entry.progress(100) : entry.error() xhr.onerror = () => entry.error() xhr.upload.addEventListener("progress", (event) => { if(event.lengthComputable){ let percent = Math.round((event.loaded / event.total) * 100) if(percent < 100){ entry.progress(percent) } } }) xhr.open("POST", url, true) xhr.send(formData) }) } export default Uploaders; ``` We define an `Uploaders.S3` function, which receives our entries. It then performs an AJAX request for each entry, using the `entry.progress()` and `entry.error()` functions to report upload events back to the LiveView. The name of the uploader must match the one we return on the `:uploader` metadata in LiveView. Finally, head over to `app.js` and add the `uploaders: Uploaders` key to the `LiveSocket` constructor to tell phoenix where to find the uploaders returned within the external metadata. ``` // for uploading to S3 import Uploaders from "./uploaders" let liveSocket = new LiveSocket("/live", Socket, { params: {_csrf_token: csrfToken}, uploaders: Uploaders } ) ``` Now "S3" returned from the server will match the one in the client. To debug client-side JavaScript when trying to upload, you can inspect your browser and look at the console or networks tab to view the error logs. ### [](external-uploads.html#direct-to-s3-compatible)Direct to S3-Compatible > This section assumes that you installed and configured [ExAws](../ex_aws/readme.html) and [ExAws.S3](../ex_aws_s3/ExAws.S3.html) correctly in your project and can execute the examples in the page without errors. Most S3 compatible platforms like Cloudflare R2 don't support `POST` when uploading files so we need to use `PUT` with a signed URL instead of the signed `POST`and send the file straight to the service, to do so we need to change the `presign_upload/2` function and the `Uploaders.S3` that does the upload. The new `presign_upload/2`: ``` def presign_upload(entry, socket) do config = ExAws.Config.new(:s3) bucket = "bucket" key = "public/#{entry.client_name}" {:ok, url} = ExAws.S3.presigned_url(config, :put, bucket, key, expires_in: 3600, query_params: [{"Content-Type", entry.client_type}] ) {:ok, %{uploader: "S3", key: key, url: url}, socket} end ``` The new `Uploaders.S3`: ``` Uploaders.S3 = function (entries, onViewError) { entries.forEach(entry => { let xhr = new XMLHttpRequest() onViewError(() => xhr.abort()) xhr.onload = () => xhr.status === 200 ? entry.progress(100) : entry.error() xhr.onerror = () => entry.error() xhr.upload.addEventListener("progress", (event) => { if(event.lengthComputable){ let percent = Math.round((event.loaded / event.total) * 100) if(percent < 100){ entry.progress(percent) } } }) let url = entry.meta.url xhr.open("PUT", url, true) xhr.send(entry.file) }) } ``` [← Previous Page Bindings](bindings.html) [Next Page → Form bindings](form-bindings.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/client/external-uploads.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/form-bindings.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/client/form-bindings.md#L1 "View Source") Form bindings ## [](form-bindings.html#form-events)Form events To handle form changes and submissions, use the `phx-change` and `phx-submit` events. In general, it is preferred to handle input changes at the form level, where all form fields are passed to the LiveView's callback given any single input change. For example, to handle real-time form validation and saving, your form would use both `phx-change` and `phx-submit` bindings. Let's get started with an example: ``` <.form for={@form} phx-change="validate" phx-submit="save"> <.input type="text" field={@form[:username]} /> <.input type="email" field={@form[:email]} /> <button>Save</button> </.form> ``` `.form` is the function component defined in [`Phoenix.Component.form/1`](Phoenix.Component.html#form/1), we recommend reading its documentation for more details on how it works and all supported options. `.form` expects a `@form` assign, which can be created from a changeset or user parameters via [`Phoenix.Component.to_form/1`](Phoenix.Component.html#to_form/1). `input/1` is a function component for rendering inputs, most often defined in your own application, often encapsulating labelling, error handling, and more. Here is a simple version to get started with: ``` attr :field, Phoenix.HTML.FormField attr :rest, :global, include: ~w(type) def input(assigns) do ~H""" <input id={@field.id} name={@field.name} value={@field.value} {@rest} /> """ end ``` ### [](form-bindings.html#the-corecomponents-module)The `CoreComponents` module If your application was generated with Phoenix v1.7, then `mix phx.new` automatically imports many ready-to-use function components, such as `.input` component with built-in features and styles. With the form rendered, your LiveView picks up the events in `handle_event` callbacks, to validate and attempt to save the parameter accordingly: ``` def render(assigns) ... def mount(_params, _session, socket) do {:ok, assign(socket, form: to_form(Accounts.change_user(%User{})))} end def handle_event("validate", %{"user" => params}, socket) do form = %User{} |> Accounts.change_user(params) |> to_form(action: :validate) {:noreply, assign(socket, form: form)} end def handle_event("save", %{"user" => user_params}, socket) do case Accounts.create_user(user_params) do {:ok, user} -> {:noreply, socket |> put_flash(:info, "user created") |> redirect(to: ~p"/users/#{user}")} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, form: to_form(changeset))} end end ``` The validate callback simply updates the changeset based on all form input values, then convert the changeset to a form and assign it to the socket. If the form changes, such as generating new errors, [`render/1`](Phoenix.LiveView.html#c:render/1) is invoked and the form is re-rendered. Likewise for `phx-submit` bindings, the same callback is invoked and persistence is attempted. On success, a `:noreply` tuple is returned and the socket is annotated for redirect with [`Phoenix.LiveView.redirect/2`](Phoenix.LiveView.html#redirect/2) to the new user page, otherwise the socket assigns are updated with the errored changeset to be re-rendered for the client. You may wish for an individual input to use its own change event or to target a different component. This can be accomplished by annotating the input itself with `phx-change`, for example: ``` <.form for={@form} phx-change="validate" phx-submit="save"> ... <.input field={@form[:email]} phx-change="email_changed" phx-target={@myself} /> </.form> ``` Then your LiveView or LiveComponent would handle the event: ``` def handle_event("email_changed", %{"user" => %{"email" => email}}, socket) do ... end ``` #### Note 1. Only the individual input is sent as params for an input marked with `phx-change`. 2. While it is possible to use `phx-change` on individual inputs, those inputs must still be within a form. ## [](form-bindings.html#error-feedback)Error feedback For proper error feedback on form updates, LiveView sends special parameters on form events starting with `_unused_` to indicate that the input for the specific field has not been interacted with yet. When creating a form from these parameters through [`Phoenix.Component.to_form/2`](Phoenix.Component.html#to_form/2) or [`Phoenix.Component.form/1`](Phoenix.Component.html#form/1), [`Phoenix.Component.used_input?/1`](Phoenix.Component.html#used_input?/1) can be used to filter error messages. For example, your `MyAppWeb.CoreComponents` may use this function: ``` def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do errors = if Phoenix.Component.used_input?(field), do: field.errors, else: [] assigns |> assign(field: nil, id: assigns.id || field.id) |> assign(:errors, Enum.map(errors, &translate_error(&1))) ``` Now only errors for fields that were interacted with are shown. ## [](form-bindings.html#number-inputs)Number inputs Number inputs are a special case in LiveView forms. On programmatic updates, some browsers will clear invalid inputs. So LiveView will not send change events from the client when an input is invalid, instead allowing the browser's native validation UI to drive user interaction. Once the input becomes valid, change and submit events will be sent normally. ``` <input type="number"> ``` This is known to have a plethora of problems including accessibility, large numbers are converted to exponential notation, and scrolling can accidentally increase or decrease the number. One alternative is the `inputmode` attribute, which may serve your application's needs and users much better. According to [Can I Use?](https://caniuse.com/#search=inputmode), the following is supported by 94% of the global market (as of Nov 2024): ``` <input type="text" inputmode="numeric" pattern="[0-9]*"> ``` ## [](form-bindings.html#password-inputs)Password inputs Password inputs are also special cased in [`Phoenix.HTML`](../phoenix_html/4.1.1/Phoenix.HTML.html). For security reasons, password field values are not reused when rendering a password input tag. This requires explicitly setting the `:value` in your markup, for example: ``` <.input field={f[:password]} value={input_value(f[:password].value)} /> <.input field={f[:password_confirmation]} value={input_value(f[:password_confirmation].value)} /> ``` ## [](form-bindings.html#nested-inputs)Nested inputs Nested inputs are handled using `.inputs_for` function component. By default it will add the necessary hidden input fields for tracking ids of Ecto associations. ``` <.inputs_for :let={fp} field={f[:friends]}> <.input field={fp[:name]} type="text" /> </.inputs_for> ``` ## [](form-bindings.html#file-inputs)File inputs LiveView forms support [reactive file inputs](uploads.html), including drag and drop support via the `phx-drop-target` attribute: ``` <div class="container" phx-drop-target={@uploads.avatar.ref}> ... <.live_file_input upload={@uploads.avatar} /> </div> ``` See [`Phoenix.Component.live_file_input/1`](Phoenix.Component.html#live_file_input/1) for more. ## [](form-bindings.html#submitting-the-form-action-over-http)Submitting the form action over HTTP The `phx-trigger-action` attribute can be added to a form to trigger a standard form submit on DOM patch to the URL specified in the form's standard `action` attribute. This is useful to perform pre-final validation of a LiveView form submit before posting to a controller route for operations that require Plug session mutation. For example, in your LiveView template you can annotate the `phx-trigger-action` with a boolean assign: ``` <.form :let={f} for={@changeset} action={~p"/users/reset_password"} phx-submit="save" phx-trigger-action={@trigger_submit}> ``` Then in your LiveView, you can toggle the assign to trigger the form with the current fields on next render: ``` def handle_event("save", params, socket) do case validate_change_password(socket.assigns.user, params) do {:ok, changeset} -> {:noreply, assign(socket, changeset: changeset, trigger_submit: true)} {:error, changeset} -> {:noreply, assign(socket, changeset: changeset)} end end ``` Once `phx-trigger-action` is true, LiveView disconnects and then submits the form. ## [](form-bindings.html#recovery-following-crashes-or-disconnects)Recovery following crashes or disconnects By default, all forms marked with `phx-change` and having `id` attribute will recover input values automatically after the user has reconnected or the LiveView has remounted after a crash. This is achieved by the client triggering the same `phx-change` to the server as soon as the mount has been completed. **Note:** if you want to see form recovery working in development, please make sure to disable live reloading in development by commenting out the LiveReload plug in your `endpoint.ex` file or by setting `code_reloader: false` in your `config/dev.exs`. Otherwise live reloading may cause the current page to be reloaded whenever you restart the server, which will discard all form state. For most use cases, this is all you need and form recovery will happen without consideration. In some cases, where forms are built step-by-step in a stateful fashion, it may require extra recovery handling on the server outside of your existing `phx-change` callback code. To enable specialized recovery, provide a `phx-auto-recover` binding on the form to specify a different event to trigger for recovery, which will receive the form params as usual. For example, imagine a LiveView wizard form where the form is stateful and built based on what step the user is on and by prior selections: ``` <form id="wizard" phx-change="validate_wizard_step" phx-auto-recover="recover_wizard"> ``` On the server, the `"validate_wizard_step"` event is only concerned with the current client form data, but the server maintains the entire state of the wizard. To recover in this scenario, you can specify a recovery event, such as `"recover_wizard"` above, which would wire up to the following server callbacks in your LiveView: ``` def handle_event("validate_wizard_step", params, socket) do # regular validations for current step {:noreply, socket} end def handle_event("recover_wizard", params, socket) do # rebuild state based on client input data up to the current step {:noreply, socket} end ``` To forgo automatic form recovery, set `phx-auto-recover="ignore"`. ## [](form-bindings.html#resetting-forms)Resetting forms To reset a LiveView form, you can use the standard `type="reset"` on a form button or input. When clicked, the form inputs will be reset to their original values. After the form is reset, a `phx-change` event is emitted with the `_target` param containing the reset `name`. For example, the following element: ``` <form phx-change="changed"> ... <button type="reset" name="reset">Reset</button> </form> ``` Can be handled on the server differently from your regular change function: ``` def handle_event("changed", %{"_target" => ["reset"]} = params, socket) do # handle form reset end def handle_event("changed", params, socket) do # handle regular form change end ``` ## [](form-bindings.html#javascript-client-specifics)JavaScript client specifics The JavaScript client is always the source of truth for current input values. For any given input with focus, LiveView will never overwrite the input's current value, even if it deviates from the server's rendered updates. This works well for updates where major side effects are not expected, such as form validation errors, or additive UX around the user's input values as they fill out a form. For these use cases, the `phx-change` input does not concern itself with disabling input editing while an event to the server is in flight. When a `phx-change` event is sent to the server, the input tag and parent form tag receive the `phx-change-loading` CSS class, then the payload is pushed to the server with a `"_target"` param in the root payload containing the keyspace of the input name which triggered the change event. For example, if the following input triggered a change event: ``` <input name="user[username]"/> ``` The server's `handle_event/3` would receive a payload: ``` %{"_target" => ["user", "username"], "user" => %{"username" => "Name"}} ``` The `phx-submit` event is used for form submissions where major side effects typically happen, such as rendering new containers, calling an external service, or redirecting to a new page. On submission of a form bound with a `phx-submit` event: 1. The form's inputs are set to `readonly` 2. Any submit button on the form is disabled 3. The form receives the `"phx-submit-loading"` class On completion of server processing of the `phx-submit` event: 1. The submitted form is reactivated and loses the `"phx-submit-loading"` class 2. The last input with focus is restored (unless another input has received focus) 3. Updates are patched to the DOM as usual To handle latent events, the `<button>` tag of a form can be annotated with `phx-disable-with`, which swaps the element's `innerText` with the provided value during event submission. For example, the following code would change the "Save" button to "Saving...", and restore it to "Save" on acknowledgment: ``` <button type="submit" phx-disable-with="Saving...">Save</button> ``` #### A note on disabled buttons By default, LiveView only disables submit buttons and inputs within forms while waiting for a server acknowledgement. If you want a button outside of a form to be disabled without changing its text, you can add `phx-disable-with` without a value: ``` <button type="button" phx-disable-with>...</button> ``` Note also that LiveView ignores clicks on elements that are currently awaiting an acknowledgement from the server. This means that although a regular button without `phx-disable-with` is not semantically disabled while waiting for a server response, it will not trigger duplicate events. You may also take advantage of LiveView's CSS loading state classes to swap out your form content while the form is submitting. For example, with the following rules in your `app.css`: ``` .while-submitting { display: none; } .inputs { display: block; } .phx-submit-loading .while-submitting { display: block; } .phx-submit-loading .inputs { display: none; } ``` You can show and hide content with the following markup: ``` <form phx-change="update"> <div class="while-submitting">Please wait while we save our content...</div> <div class="inputs"> <input type="text" name="text" value={@text}> </div> </form> ``` Additionally, we strongly recommend including a unique HTML "id" attribute on the form. When DOM siblings change, elements without an ID will be replaced rather than moved, which can cause issues such as form fields losing focus. ## [](form-bindings.html#triggering-phx-form-events-with-javascript)Triggering `phx-` form events with JavaScript Often it is desirable to trigger an event on a DOM element without explicit user interaction on the element. For example, a custom form element such as a date picker or custom select input which utilizes a hidden input element to store the selected state. In these cases, the event functions on the DOM API can be used, for example to trigger a `phx-change` event: ``` document.getElementById("my-select").dispatchEvent( new Event("input", {bubbles: true}) ) ``` When using a client hook, `this.el` can be used to determine the element as outlined in the "Client hooks" documentation. It is also possible to trigger a `phx-submit` using a "submit" event: ``` document.getElementById("my-form").dispatchEvent( new Event("submit", {bubbles: true, cancelable: true}) ) ``` [← Previous Page External uploads](external-uploads.html) [Next Page → JavaScript interoperability](js-interop.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/client/form-bindings.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/gettext.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/gettext.md#L1 "View Source") Gettext for internationalization For internationalization with [gettext](../gettext/Gettext.html), you must call `Gettext.put_locale/2` on the LiveView mount callback to instruct the LiveView which locale should be used for rendering the page. However, one question that has to be answered is how to retrieve the locale in the first place. There are many approaches to solve this problem: 1. The locale could be stored in the URL as a parameter 2. The locale could be stored in the session 3. The locale could be stored in the database We will briefly cover these approaches to provide some direction. ## [](gettext.html#locale-from-parameters)Locale from parameters You can say all URLs have a locale parameter. In your router: ``` scope "/:locale" do live ... get ... end ``` Accessing a page without a locale should automatically redirect to a URL with locale (the best locale could be fetched from HTTP headers, which is outside of the scope of this guide). Then, assuming all URLs have a locale, you can set the Gettext locale accordingly: ``` def mount(%{"locale" => locale}, _session, socket) do Gettext.put_locale(MyApp.Gettext, locale) {:ok, socket} end ``` You can also use the [`on_mount`](Phoenix.LiveView.html#on_mount/1) hook to automatically restore the locale for every LiveView in your application: ``` defmodule MyAppWeb.RestoreLocale do def on_mount(:default, %{"locale" => locale}, _session, socket) do Gettext.put_locale(MyApp.Gettext, locale) {:cont, socket} end # catch-all case def on_mount(:default, _params, _session, socket), do: {:cont, socket} end ``` Then, add this hook to `def live_view` under `MyAppWeb`, to run it on all LiveViews by default: ``` def live_view do quote do use Phoenix.LiveView, layout: {MyAppWeb.Layouts, :app} on_mount MyAppWeb.RestoreLocale unquote(view_helpers()) end end ``` Note that, because the Gettext locale is not stored in the assigns, if you want to change the locale, you must use `<.link navigate={...} />`, instead of simply patching the page. ## [](gettext.html#locale-from-session)Locale from session You may also store the locale in the Plug session. For example, in a controller you might do: ``` def put_user_session(conn, current_user) do Gettext.put_locale(MyApp.Gettext, current_user.locale) conn |> put_session(:user_id, current_user.id) |> put_session(:locale, current_user.locale) end ``` and then restore the locale from session within your LiveView mount: ``` def mount(_params, %{"locale" => locale}, socket) do Gettext.put_locale(MyApp.Gettext, locale) {:ok, socket} end ``` You can also encapsulate this in a hook, as done in the previous section. However, if the locale is stored in the session, you can only change it by using regular controller requests. Therefore you should always use `<.link to={...} />` to point to a controller that change the session accordingly, reloading any LiveView. ## [](gettext.html#locale-from-database)Locale from database You may also allow users to store their locale configuration in the database. Then, on `mount/3`, you can retrieve the user id from the session and load the locale: ``` def mount(_params, %{"user_id" => user_id}, socket) do user = Users.get_user!(user_id) Gettext.put_locale(MyApp.Gettext, user.locale) {:ok, socket} end ``` In practice, you may end-up mixing more than one approach listed here. For example, reading from the database is great once the user is logged in but, before that happens, you may need to store the locale in the session or in the URL. Similarly, you can keep the locale in the URL, but change the URL accordingly to the user preferred locale once they sign in. Hopefully this guide gives some suggestions on how to move forward and explore the best approach for your application. [← Previous Page Error and exception handling](error-handling.html) [Next Page → Live layouts](live-layouts.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/gettext.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/html-attrs.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/cheatsheets/html-attrs.cheatmd#L1 "View Source") Print cheatsheet `phx-` HTML attributes A summary of special HTML attributes used in Phoenix LiveView templates. Each attribute is linked to its documentation for more details. ## [](html-attrs.html#event-handlers)Event Handlers Attribute values can be: - An event name for the [`handle_event`](Phoenix.LiveView.html#c:handle_event/3) server callback - [JS commands](bindings.html#js-commands) to be executed directly on the client > Use [`phx-value-*`](bindings.html#click-events) attributes to pass params to the server. > Use [`phx-debounce` and `phx-throttle`](bindings.html#rate-limiting-events-with-debounce-and-throttle) to control the frequency of events. ### [](html-attrs.html#click)Click Attributes[`phx-click`](bindings.html#click-events) [`phx-click-away`](bindings.html#click-events) ### [](html-attrs.html#focus)Focus Attributes[`phx-blur`](bindings.html#focus-and-blur-events) [`phx-focus`](bindings.html#focus-and-blur-events)[`phx-window-blur`](bindings.html#focus-and-blur-events) [`phx-window-focus`](bindings.html#focus-and-blur-events) ### [](html-attrs.html#keyboard)Keyboard Attributes[`phx-keydown`](bindings.html#key-events) [`phx-keyup`](bindings.html#key-events)[`phx-window-keydown`](bindings.html#key-events) [`phx-window-keyup`](bindings.html#key-events) > Use the `phx-key` attribute to listen to specific keys. ### [](html-attrs.html#scroll)Scroll Attributes[`phx-viewport-top`](bindings.html#scroll-events-and-infinite-stream-pagination) [`phx-viewport-bottom`](bindings.html#scroll-events-and-infinite-stream-pagination) ### [](html-attrs.html#example)Example #### lib/hello\_web/live/hello\_live.html.heex ``` <button type="button" phx-click="click" phx-value-user={@current_user.id}>Click Me</button> <button type="button" phx-click={JS.toggle(to: "#example")}>Toggle</button> ``` ## [](html-attrs.html#form-event-handlers)Form Event Handlers ### [](html-attrs.html#on-form-elements)On `<form>` elements AttributeValue[`phx-change`](form-bindings.html)Event name or [JS commands](bindings.html#js-commands)[`phx-submit`](form-bindings.html)Event name or [JS commands](bindings.html#js-commands)[`phx-auto-recover`](form-bindings.html)Event name, [JS commands](bindings.html#js-commands) or `"ignore"`[`phx-trigger-action`](form-bindings.html)`true` or `false` ### [](html-attrs.html#on-button-elements)On `<button>` elements AttributeValue[`phx-disable-with`](form-bindings.html#javascript-client-specifics)Text to show during event submission ### [](html-attrs.html#form-example)Form Example #### lib/hello\_web/live/hello\_live.html.heex ``` <form phx-change="validate" phx-submit="save"> <input type="text" name="name" phx-debounce="500" phx-throttle="500" /> <button type="submit" phx-disable-with="Saving...">Save</button> </form> ``` ## [](html-attrs.html#socket-connection-lifecycle)Socket Connection Lifecycle AttributeValue[`phx-connected`](bindings.html#lifecycle-events)[JS commands](bindings.html#js-commands) executed after the [`LiveSocket`](js-interop.html) connects[`phx-disconnected`](bindings.html#lifecycle-events)[JS commands](bindings.html#js-commands) executed after the [`LiveSocket`](js-interop.html) disconnects #### lib/hello\_web/live/hello\_live.html.heex ``` <div id="status" class="hidden" phx-disconnected={JS.show()} phx-connected={JS.hide()}> Attempting to reconnect... </div> ``` ## [](html-attrs.html#dom-element-lifecycle)DOM Element Lifecycle AttributeValue[`phx-mounted`](bindings.html#dom-patching)[JS commands](bindings.html#js-commands) executed after the element is mounted[`phx-remove`](bindings.html#dom-patching)[JS commands](bindings.html#js-commands) executed during the element removal[`phx-update`](bindings.html#dom-patching)`"replace"` (default), `"stream"` or `"ignore"`, configures DOM patching behavior #### lib/hello\_web/live/hello\_live.html.heex ``` <div id="iframe-container" phx-mounted={JS.transition("animate-bounce", time: 2000)} phx-remove={JS.hide(transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"})} > <button type="button" phx-click={JS.exec("phx-remove", to: "#iframe-container")}>Hide</button> <iframe id="iframe" src="https://example.com" phx-update="ignore"></iframe> </div> ``` ## [](html-attrs.html#client-hooks)Client Hooks AttributeValue[`phx-hook`](js-interop.html#client-hooks-via-phx-hook)The name of a previously defined JavaScript hook in the [`LiveSocket`](js-interop.html) Client hooks provide bidirectional communication between client and server using `this.pushEvent` and `this.handleEvent` to send and receive events. #### lib/hello\_web/live/hello\_live.html.heex ``` <div id="example" phx-hook="Example"> <h1>Events</h1> <ul id="example-events"></ul> </div> ``` #### assets/js/app.js ``` let Hooks = {} Hooks.Example = { // Callbacks mounted() { this.appendEvent("Mounted") }, beforeUpdate() { this.appendEvent("Before Update") }, updated() { this.appendEvent("Updated") }, destroyed() { this.appendEvent("Destroyed") }, disconnected() { this.appendEvent("Disconnected") }, reconnected() { this.appendEvent("Reconnected") }, // Custom Helper appendEvent(name) { console.log(name) let li = document.createElement("li") li.innerText = name this.el.querySelector("#example-events").appendChild(li) } } let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks}) ``` ## [](html-attrs.html#tracking-static-assets)Tracking Static Assets AttributeValue[`phx-track-static`](Phoenix.LiveView.html#static_changed?/1)None, used to annotate static files #### lib/hello\_web/components/layouts/root.html.heex ``` <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /> <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}></script> ``` [← Previous Page Syncing changes and optimistic UIs](syncing-changes.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/cheatsheets/html-attrs.cheatmd)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/index.html.md === === phx-docs/phoenix_live_view/js-interop.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/client/js-interop.md#L1 "View Source") JavaScript interoperability To enable LiveView client/server interaction, we instantiate a LiveSocket. For example: ``` import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) liveSocket.connect() ``` All options are passed directly to the [`Phoenix.Socket`](../phoenix/1.7.18/Phoenix.Socket.html) constructor, except for the following LiveView specific options: - `bindingPrefix` - the prefix to use for phoenix bindings. Defaults `"phx-"` - `params` - the `connect_params` to pass to the view's mount callback. May be a literal object or closure returning an object. When a closure is provided, the function receives the view's element. - `hooks` - a reference to a user-defined hooks namespace, containing client callbacks for server/client interop. See the [Client hooks](js-interop.html#client-hooks-via-phx-hook) section below for details. - `uploaders` - a reference to a user-defined uploaders namespace, containing client callbacks for client-side direct-to-cloud uploads. See the [External uploads guide](external-uploads.html) for details. - `metadata` - additional user-defined metadata that is sent along events to the server. See the [Key events](bindings.html#key-events) section in the bindings guide for an example. ## [](js-interop.html#debugging-client-events)Debugging client events To aid debugging on the client when troubleshooting issues, the `enableDebug()` and `disableDebug()` functions are exposed on the `LiveSocket` JavaScript instance. Calling `enableDebug()` turns on debug logging which includes LiveView life-cycle and payload events as they come and go from client to server. In practice, you can expose your instance on `window` for quick access in the browser's web console, for example: ``` // app.js let liveSocket = new LiveSocket(...) liveSocket.connect() window.liveSocket = liveSocket // in the browser's web console >> liveSocket.enableDebug() ``` The debug state uses the browser's built-in `sessionStorage`, so it will remain in effect for as long as your browser session lasts. ## [](js-interop.html#simulating-latency)Simulating Latency Proper handling of latency is critical for good UX. LiveView's CSS loading states allow the client to provide user feedback while awaiting a server response. In development, near zero latency on localhost does not allow latency to be easily represented or tested, so LiveView includes a latency simulator with the JavaScript client to ensure your application provides a pleasant experience. Like the `enableDebug()` function above, the `LiveSocket` instance includes `enableLatencySim(milliseconds)` and `disableLatencySim()` functions which apply throughout the current browser session. The `enableLatencySim` function accepts an integer in milliseconds for the one-way latency to and from the server. For example: ``` // app.js let liveSocket = new LiveSocket(...) liveSocket.connect() window.liveSocket = liveSocket // in the browser's web console >> liveSocket.enableLatencySim(1000) [Log] latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable ``` ## [](js-interop.html#handling-server-pushed-events)Handling server-pushed events When the server uses [`Phoenix.LiveView.push_event/3`](Phoenix.LiveView.html#push_event/3), the event name will be dispatched in the browser with the `phx:` prefix. For example, imagine the following template where you want to highlight an existing element from the server to draw the user's attention: ``` <div id={"item-#{item.id}"} class="item"> {item.title} </div> ``` Next, the server can issue a highlight using the standard `push_event`: ``` def handle_info({:item_updated, item}, socket) do {:noreply, push_event(socket, "highlight", %{id: "item-#{item.id}"})} end ``` Finally, a window event listener can listen for the event and conditionally execute the highlight command if the element matches: ``` let liveSocket = new LiveSocket(...) window.addEventListener("phx:highlight", (e) => { let el = document.getElementById(e.detail.id) if(el) { // logic for highlighting } }) ``` If you desire, you can also integrate this functionality with Phoenix' JS commands, executing JS commands for the given element whenever highlight is triggered. First, update the element to embed the JS command into a data attribute: ``` <div id={"item-#{item.id}"} class="item" data-highlight={JS.transition("highlight")}> {item.title} </div> ``` Now, in the event listener, use `LiveSocket.execJS` to trigger all JS commands in the new attribute: ``` let liveSocket = new LiveSocket(...) window.addEventListener("phx:highlight", (e) => { document.querySelectorAll(`[data-highlight]`).forEach(el => { if(el.id == e.detail.id){ liveSocket.execJS(el, el.getAttribute("data-highlight")) } }) }) ``` ## [](js-interop.html#client-hooks-via-phx-hook)Client hooks via `phx-hook` To handle custom client-side JavaScript when an element is added, updated, or removed by the server, a hook object may be provided via `phx-hook`. `phx-hook` must point to an object with the following life-cycle callbacks: - `mounted` - the element has been added to the DOM and its server LiveView has finished mounting - `beforeUpdate` - the element is about to be updated in the DOM. *Note*: any call here must be synchronous as the operation cannot be deferred or cancelled. - `updated` - the element has been updated in the DOM by the server - `destroyed` - the element has been removed from the page, either by a parent update, or by the parent being removed entirely - `disconnected` - the element's parent LiveView has disconnected from the server - `reconnected` - the element's parent LiveView has reconnected to the server *Note:* When using hooks outside the context of a LiveView, `mounted` is the only callback invoked, and only those elements on the page at DOM ready will be tracked. For dynamic tracking of the DOM as elements are added, removed, and updated, a LiveView should be used. The above life-cycle callbacks have in-scope access to the following attributes: - `el` - attribute referencing the bound DOM node - `liveSocket` - the reference to the underlying `LiveSocket` instance - `pushEvent(event, payload, (reply, ref) => ...)` - method to push an event from the client to the LiveView server - `pushEventTo(selectorOrTarget, event, payload, (reply, ref) => ...)` - method to push targeted events from the client to LiveViews and LiveComponents. It sends the event to the LiveComponent or LiveView the `selectorOrTarget` is defined in, where its value can be either a query selector or an actual DOM element. If the query selector returns more than one element it will send the event to all of them, even if all the elements are in the same LiveComponent or LiveView. `pushEventTo` supports passing the node element e.g. `this.el` instead of selector e.g. `"#" + this.el.id` as the first parameter for target. - `handleEvent(event, (payload) => ...)` - method to handle an event pushed from the server - `upload(name, files)` - method to inject a list of file-like objects into an uploader. - `uploadTo(selectorOrTarget, name, files)` - method to inject a list of file-like objects into an uploader. The hook will send the files to the uploader with `name` defined by [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3) on the server-side. Dispatching new uploads triggers an input change event which will be sent to the LiveComponent or LiveView the `selectorOrTarget` is defined in, where its value can be either a query selector or an actual DOM element. If the query selector returns more than one live file input, an error will be logged. For example, the markup for a controlled input for phone-number formatting could be written like this: ``` <input type="text" name="user[phone_number]" id="user-phone-number" phx-hook="PhoneNumber" /> ``` Then a hook callback object could be defined and passed to the socket: ``` /** * @type {Object.<string, import("phoenix_live_view").ViewHook>} */ let Hooks = {} Hooks.PhoneNumber = { mounted() { this.el.addEventListener("input", e => { let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/) if(match) { this.el.value = `${match[1]}-${match[2]}-${match[3]}` } }) } } let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, ...}) ... ``` *Note*: when using `phx-hook`, a unique DOM ID must always be set. For integration with client-side libraries which require a broader access to full DOM management, the `LiveSocket` constructor accepts a `dom` option with an `onBeforeElUpdated` callback. The `fromEl` and `toEl` DOM nodes are passed to the function just before the DOM patch operations occurs in LiveView. This allows external libraries to (re)initialize DOM elements or copy attributes as necessary as LiveView performs its own patch operations. The update operation cannot be cancelled or deferred, and the return value is ignored. For example, the following option could be used to guarantee that some attributes set on the client-side are kept intact: ``` ... let liveSocket = new LiveSocket("/live", Socket, { params: {_csrf_token: csrfToken}, hooks: Hooks, dom: { onBeforeElUpdated(from, to) { for (const attr of from.attributes) { if (attr.name.startsWith("data-js-")) { to.setAttribute(attr.name, attr.value); } } } } } ``` In the example above, all attributes starting with `data-js-` won't be replaced when the DOM is patched by LiveView. ### [](js-interop.html#client-server-communication)Client-server communication A hook can push events to the LiveView by using the `pushEvent` function and receive a reply from the server via a `{:reply, map, socket}` return value. The reply payload will be passed to the optional `pushEvent` response callback. Communication with the hook from the server can be done by reading data attributes on the hook element or by using [`Phoenix.LiveView.push_event/3`](Phoenix.LiveView.html#push_event/3) on the server and `handleEvent` on the client. For example, to implement infinite scrolling, one can pass the current page using data attributes: ``` <div id="infinite-scroll" phx-hook="InfiniteScroll" data-page={@page}> ``` And then in the client: ``` /** * @type {import("phoenix_live_view").ViewHook} */ Hooks.InfiniteScroll = { page() { return this.el.dataset.page }, mounted(){ this.pending = this.page() window.addEventListener("scroll", e => { if(this.pending == this.page() && scrollAt() > 90){ this.pending = this.page() + 1 this.pushEvent("load-more", {}) } }) }, updated(){ this.pending = this.page() } } ``` However, the data attribute approach is not a good approach if you need to frequently push data to the client. To push out-of-band events to the client, for example to render charting points, one could do: ``` <div id="chart" phx-hook="Chart"> ``` And then on the client: ``` /** * @type {import("phoenix_live_view").ViewHook} */ Hooks.Chart = { mounted(){ this.handleEvent("points", ({points}) => MyChartLib.addPoints(points)) } } ``` And then you can push events as: ``` {:noreply, push_event(socket, "points", %{points: new_points})} ``` Events pushed from the server via `push_event` are global and will be dispatched to all active hooks on the client who are handling that event. If you need to scope events (for example when pushing from a live component that has siblings on the current live view), then this must be done by namespacing them: ``` def update(%{id: id, points: points} = assigns, socket) do socket = socket |> assign(assigns) |> push_event("points-#{id}", points) {:ok, socket} end ``` And then on the client: ``` Hooks.Chart = { mounted(){ this.handleEvent(`points-${this.el.id}`, (points) => MyChartLib.addPoints(points)); } } ``` *Note*: In case a LiveView pushes events and renders content, `handleEvent` callbacks are invoked after the page is updated. Therefore, if the LiveView redirects at the same time it pushes events, callbacks won't be invoked on the old page's elements. Callbacks would be invoked on the redirected page's newly mounted hook elements. [← Previous Page Form bindings](form-bindings.html) [Next Page → Syncing changes and optimistic UIs](syncing-changes.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/client/js-interop.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/live-layouts.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/live-layouts.md#L1 "View Source") Live layouts From Phoenix v1.7, your application is made of two layouts: - the root layout - this is a layout used by both LiveView and regular views. This layout typically contains the `<html>` definition alongside the head and body tags. Any content defined in the root layout will remain the same, even as you live navigate across LiveViews. The root layout is typically declared on the router with `put_root_layout` and defined as "root.html.heex" in your layouts folder - the app layout - this is the default application layout which is rendered on both regular HTTP requests and LiveViews. It defaults to "app.html.heex" Overall, those layouts are found in `components/layouts` and are embedded within `MyAppWeb.Layouts`. All layouts must call `{@inner_content}` to inject the content rendered by the layout. ## [](live-layouts.html#root-layout)Root layout The "root" layout is rendered only on the initial request and therefore it has access to the `@conn` assign. The root layout is typically defined in your router: ``` plug :put_root_layout, html: {MyAppWeb.Layouts, :root} ``` The root layout can also be set via the `:root_layout` option in your router via [`Phoenix.LiveView.Router.live_session/2`](Phoenix.LiveView.Router.html#live_session/2). ## [](live-layouts.html#application-layout)Application layout The "app.html.heex" layout is rendered with either `@conn` or `@socket`. Both Controllers and LiveViews explicitly define the default layouts they will use. See the `def controller` and `def live_view` definitions in your `MyAppWeb` to learn how it is included. For LiveViews, the default layout can be overridden in two different ways for flexibility: 1. The `:layout` option in [`Phoenix.LiveView.Router.live_session/2`](Phoenix.LiveView.Router.html#live_session/2), when set, will override the `:layout` option given via `use Phoenix.LiveView` 2. The `:layout` option returned on mount, via `{:ok, socket, layout: ...}` will override any previously set layout option The LiveView itself will be rendered inside the layout wrapped by the `:container` tag. ## [](live-layouts.html#updating-document-title)Updating document title Because the root layout from the Plug pipeline is rendered outside of LiveView, the contents cannot be dynamically changed. The one exception is the `<title>` of the HTML document. Phoenix LiveView special cases the `@page_title` assign to allow dynamically updating the title of the page, which is useful when using live navigation, or annotating the browser tab with a notification. For example, to update the user's notification count in the browser's title bar, first set the `page_title` assign on mount: ``` def mount(_params, _session, socket) do socket = assign(socket, page_title: "Latest Posts") {:ok, socket} end ``` Then access `@page_title` in the root layout: ``` <title>{@page_title}</title> ``` You can also use the [`Phoenix.Component.live_title/1`](Phoenix.Component.html#live_title/1) component to support adding automatic prefix and suffix to the page title when rendered and on subsequent updates: ``` <Phoenix.Component.live_title default="Welcome" prefix="MyApp – "> {assigns[:page_title]} </Phoenix.Component.live_title> ``` Although the root layout is not updated by LiveView, by simply assigning to `page_title`, LiveView knows you want the title to be updated: ``` def handle_info({:new_messages, count}, socket) do {:noreply, assign(socket, page_title: "Latest Posts (#{count} new)")} end ``` *Note*: If you find yourself needing to dynamically patch other parts of the base layout, such as injecting new scripts or styles into the `<head>` during live navigation, *then a regular, non-live, page navigation should be used instead*. Assigning the `@page_title` updates the `document.title` directly, and therefore cannot be used to update any other part of the base layout. [← Previous Page Gettext for internationalization](gettext.html) [Next Page → Live navigation](live-navigation.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/live-layouts.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/live-navigation.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/live-navigation.md#L1 "View Source") Live navigation LiveView provides functionality to allow page navigation using the [browser's pushState API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). With live navigation, the page is updated without a full page reload. You can trigger live navigation in two ways: - From the client - this is done by passing either `patch={url}` or `navigate={url}` to the [`Phoenix.Component.link/1`](Phoenix.Component.html#link/1) component. - From the server - this is done by [`Phoenix.LiveView.push_patch/2`](Phoenix.LiveView.html#push_patch/2) or [`Phoenix.LiveView.push_navigate/2`](Phoenix.LiveView.html#push_navigate/2). For example, instead of writing the following in a template: ``` <.link href={~p"/pages/#{@page + 1}"}>Next</.link> ``` You would write: ``` <.link patch={~p"/pages/#{@page + 1}"}>Next</.link> ``` Or in a LiveView: ``` {:noreply, push_patch(socket, to: ~p"/pages/#{@page + 1}")} ``` The "patch" operations must be used when you want to navigate to the current LiveView, simply updating the URL and the current parameters, without mounting a new LiveView. When patch is used, the [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) callback is invoked and the minimal set of changes are sent to the client. See the next section for more information. The "navigate" operations must be used when you want to dismount the current LiveView and mount a new one. You can only "navigate" between LiveViews in the same session. While redirecting, a `phx-loading` class is added to the LiveView, which can be used to indicate to the user a new page is being loaded. If you attempt to patch to another LiveView or navigate across live sessions, a full page reload is triggered. This means your application continues to work, in case your application structure changes and that's not reflected in the navigation. Here is a quick breakdown: - `<.link href={...}>` and [`redirect/2`](../phoenix/1.7.18/Phoenix.Controller.html#redirect/2) are HTTP-based, work everywhere, and perform full page reloads - `<.link navigate={...}>` and [`push_navigate/2`](Phoenix.LiveView.html#push_navigate/2) work across LiveViews in the same session. They mount a new LiveView while keeping the current layout - `<.link patch={...}>` and [`push_patch/2`](Phoenix.LiveView.html#push_patch/2) updates the current LiveView and sends only the minimal diff while also maintaining the scroll position ## [](live-navigation.html#handle_params-3)`handle_params/3` The [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) callback is invoked after [`mount/3`](Phoenix.LiveView.html#c:mount/3) and before the initial render. It is also invoked every time `<.link patch={...}>` or [`push_patch/2`](Phoenix.LiveView.html#push_patch/2) are used. It receives the request parameters as first argument, the url as second, and the socket as third. For example, imagine you have a `UserTable` LiveView to show all users in the system and you define it in the router as: ``` live "/users", UserTable ``` Now to add live sorting, you could do: ``` <.link patch={~p"/users?sort_by=name"}>Sort by name</.link> ``` When clicked, since we are navigating to the current LiveView, [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) will be invoked. Remember you should never trust the received params, so you must use the callback to validate the user input and change the state accordingly: ``` def handle_params(params, _uri, socket) do socket = case params["sort_by"] do sort_by when sort_by in ~w(name company) -> assign(socket, sort_by: sort_by) _ -> socket end {:noreply, load_users(socket)} end ``` Note we returned `{:noreply, socket}`, where `:noreply` means no additional information is sent to the client. As with other `handle_*` callbacks, changes to the state inside [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) will trigger a new server render. Note the parameters given to [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) are the same as the ones given to [`mount/3`](Phoenix.LiveView.html#c:mount/3). So how do you decide which callback to use to load data? Generally speaking, data should always be loaded on [`mount/3`](Phoenix.LiveView.html#c:mount/3), since [`mount/3`](Phoenix.LiveView.html#c:mount/3) is invoked once per LiveView life-cycle. Only the params you expect to be changed via `<.link patch={...}>` or [`push_patch/2`](Phoenix.LiveView.html#push_patch/2) must be loaded on [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3). For example, imagine you have a blog. The URL for a single post is: "/blog/posts/:post\_id". In the post page, you have comments and they are paginated. You use `<.link patch={...}>` to update the shown comments every time the user paginates, updating the URL to "/blog/posts/:post\_id?page=X". In this example, you will access `"post_id"` on [`mount/3`](Phoenix.LiveView.html#c:mount/3) and the page of comments on [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3). ## [](live-navigation.html#replace-page-address)Replace page address LiveView also allows the current browser URL to be replaced. This is useful when you want certain events to change the URL but without polluting the browser's history. This can be done by passing the `<.link replace>` option to any of the navigation helpers. ## [](live-navigation.html#multiple-liveviews-in-the-same-page)Multiple LiveViews in the same page LiveView allows you to have multiple LiveViews in the same page by calling [`Phoenix.Component.live_render/3`](Phoenix.Component.html#live_render/3) in your templates. However, only the LiveViews defined directly in your router can use the "Live Navigation" functionality described here. This is important because LiveViews work closely with your router, guaranteeing you can only navigate to known routes. [← Previous Page Live layouts](live-layouts.html) [Next Page → Security considerations](security-model.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/live-navigation.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/security-model.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/security-model.md#L1 "View Source") Security considerations LiveView begins its life-cycle as a regular HTTP request. Then a stateful connection is established. Both the HTTP request and the stateful connection receive the client data via parameters and session. This means that any session validation must happen both in the HTTP request (plug pipeline) and the stateful connection (LiveView mount). ## [](security-model.html#authentication-vs-authorization)Authentication vs authorization When speaking about security, there are two terms commonly used: authentication and authorization. Authentication is about identifying a user. Authorization is about telling if a user has access to a certain resource or feature in the system. In a regular web application, once a user is authenticated, for example by entering their email and password, or by using a third-party service such as Google, Twitter, or Facebook, a token identifying the user is stored in the session, which is a cookie (a key-value pair) stored in the user's browser. Every time there is a request, we read the value from the session, and, if valid, we fetch the user stored in the session from the database. The session is automatically validated by Phoenix and tools like [`mix phx.gen.auth`](../phoenix/1.7.18/Mix.Tasks.Phx.Gen.Auth.html) can generate the building blocks of an authentication system for you. Once the user is authenticated, they may perform many actions on the page, and some of those actions require specific permissions. This is called authorization and the specific rules often change per application. In a regular web application, we perform authentication and authorization checks on every request. Given LiveViews start as a regular HTTP request, they share the authentication logic with regular requests through plugs. The request starts in your endpoint, which then invokes the router. Plugs are used to ensure the user is authenticated and stores the relevant information in the session. Once the user is authenticated, we typically validate the sessions on the `mount` callback. Authorization rules generally happen on `mount` (for instance, is the user allowed to see this page?) and also on `handle_event` (is the user allowed to delete this item?). ## [](security-model.html#live_session)`live_session` The primary mechanism for grouping LiveViews is via the [`Phoenix.LiveView.Router.live_session/2`](Phoenix.LiveView.Router.html#live_session/2). LiveView will then ensure that navigation events within the same `live_session` skip the regular HTTP requests without going through the plug pipeline. Events across live sessions will go through the router. For example, imagine you need to authenticate two distinct types of users. Your regular users login via email and password, and you have an admin dashboard that uses HTTP auth. You can specify different `live_session`s for each authentication flow: ``` live_session :default do scope "/" do pipe_through [:authenticate_user] get ... live ... end end live_session :admin do scope "/admin" do pipe_through [:http_auth_admin] get ... live ... end end ``` Now every time you try to navigate to an admin panel, and out of it, a regular page navigation will happen and a brand new live connection will be established. It is worth remembering that LiveViews require their own security checks, so we use `pipe_through` above to protect the regular routes (get, post, etc.) and the LiveViews should run their own checks on the `mount` callback (or using [`Phoenix.LiveView.on_mount/1`](Phoenix.LiveView.html#on_mount/1) hooks). For this purpose, you can combine `live_session` with `on_mount`, as well as other options, such as the `:root_layout`. Instead of declaring `on_mount` on every LiveView, you can declare it at the router level and it will enforce it on all LiveViews under it: ``` live_session :default, on_mount: MyAppWeb.UserLiveAuth do scope "/" do pipe_through [:authenticate_user] live ... end end live_session :admin, on_mount: MyAppWeb.AdminLiveAuth do scope "/admin" do pipe_through [:authenticate_admin] live ... end end ``` Each live route under the `:default` `live_session` will invoke the `MyAppWeb.UserLiveAuth` hook on mount. This module was defined earlier in this guide. We will also pipe regular web requests through `:authenticate_user`, which must execute the same checks as `MyAppWeb.UserLiveAuth`, but tailored to plug. Similarly, the `:admin` `live_session` has its own authentication flow, powered by `MyAppWeb.AdminLiveAuth`. It also defines a plug equivalent named `:authenticate_admin`, which will be used by any regular request. If there are no regular web requests defined under a live session, then the `pipe_through` checks are not necessary. Declaring the `on_mount` on `live_session` is exactly the same as declaring it in each LiveView. Let's talk about which logic we typically execute on mount. ## [](security-model.html#mounting-considerations)Mounting considerations The [`mount/3`](Phoenix.LiveView.html#c:mount/3) callback is invoked both on the initial HTTP mount and when LiveView is connected. Therefore, any authorization performed during mount will cover all scenarios. Once the user is authenticated and stored in the session, the logic to fetch the user and further authorize its account needs to happen inside LiveView. For example, if you have the following plugs: ``` plug :ensure_user_authenticated plug :ensure_user_confirmed ``` Then the [`mount/3`](Phoenix.LiveView.html#c:mount/3) callback of your LiveView should execute those same verifications: ``` def mount(_params, %{"user_id" => user_id} = _session, socket) do socket = assign(socket, current_user: Accounts.get_user!(user_id)) socket = if socket.assigns.current_user.confirmed_at do socket else redirect(socket, to: "/login") end {:ok, socket} end ``` The `on_mount` hook allows you to encapsulate this logic and execute it on every mount: ``` defmodule MyAppWeb.UserLiveAuth do import Phoenix.Component import Phoenix.LiveView alias MyAppWeb.Accounts # from `mix phx.gen.auth` def on_mount(:default, _params, %{"user_token" => user_token} = _session, socket) do socket = assign_new(socket, :current_user, fn -> Accounts.get_user_by_session_token(user_token) end) if socket.assigns.current_user.confirmed_at do {:cont, socket} else {:halt, redirect(socket, to: "/login")} end end end ``` We use [`assign_new/3`](Phoenix.Component.html#assign_new/3). This is a convenience to avoid fetching the `current_user` multiple times across parent-child LiveViews. Now we can use the hook whenever relevant. One option is to specify the hook in your router under `live_session`: ``` live_session :default, on_mount: MyAppWeb.UserLiveAuth do # Your routes end ``` Alternatively, you can either specify the hook directly in the LiveView: ``` defmodule MyAppWeb.PageLive do use MyAppWeb, :live_view on_mount MyAppWeb.UserLiveAuth ... end ``` If you prefer, you can add the hook to `def live_view` under `MyAppWeb`, to run it on all LiveViews by default: ``` def live_view do quote do use Phoenix.LiveView, layout: {MyAppWeb.Layouts, :app} on_mount MyAppWeb.UserLiveAuth unquote(html_helpers()) end end ``` ## [](security-model.html#events-considerations)Events considerations Every time the user performs an action on your system, you should verify if the user is authorized to do so, regardless if you are using LiveViews or not. For example, imagine a user can see all projects in a web application, but they may not have permission to delete any of them. At the UI level, you handle this accordingly by not showing the delete button in the projects listing, but a savvy user can directly talk to the server and request a deletion anyway. For this reason, **you must always verify permissions on the server**. In LiveView, most actions are handled by the `handle_event` callback. Therefore, you typically authorize the user within those callbacks. In the scenario just described, one might implement this: ``` on_mount MyAppWeb.UserLiveAuth def mount(_params, _session, socket) do {:ok, load_projects(socket)} end def handle_event("delete_project", %{"project_id" => project_id}, socket) do Project.delete!(socket.assigns.current_user, project_id) {:noreply, update(socket, :projects, &Enum.reject(&1, fn p -> p.id == project_id end)} end defp load_projects(socket) do projects = Project.all_projects(socket.assigns.current_user) assign(socket, projects: projects) end ``` First, we used `on_mount` to authenticate the user based on the data stored in the session. Then we load all projects based on the authenticated user. Now, whenever there is a request to delete a project, we still pass the current user as argument to the `Project` context, so it verifies if the user is allowed to delete it or not. In case it cannot delete, it is fine to just raise an exception. After all, users are not meant to trigger this code path anyway (unless they are fiddling with something they are not supposed to!). ## [](security-model.html#disconnecting-all-instances-of-a-live-user)Disconnecting all instances of a live user So far, the security model between LiveView and regular web applications have been remarkably similar. After all, we must always authenticate and authorize every user. The main difference between them happens on logout or when revoking access. Because LiveView is a permanent connection between client and server, if a user is logged out, or removed from the system, this change won't reflect on the LiveView part unless the user reloads the page. Luckily, it is possible to address this by setting a `live_socket_id` in the session. For example, when logging in a user, you could do: ``` conn |> put_session(:current_user_id, user.id) |> put_session(:live_socket_id, "users_socket:#{user.id}") ``` Now all LiveView sockets will be identified and listen to the given `live_socket_id`. You can then disconnect all live users identified by said ID by broadcasting on the topic: ``` MyAppWeb.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{}) ``` > Note: If you use [`mix phx.gen.auth`](../phoenix/1.7.18/Mix.Tasks.Phx.Gen.Auth.html) to generate your authentication system, lines to that effect are already present in the generated code. The generated code uses a `user_token` instead of referring to the `user_id`. Once a LiveView is disconnected, the client will attempt to reestablish the connection and re-execute the [`mount/3`](Phoenix.LiveView.html#c:mount/3) callback. In this case, if the user is no longer logged in or it no longer has access to the current resource, `mount/3` will fail and the user will be redirected. This is the same mechanism provided by [`Phoenix.Channel`](../phoenix/1.7.18/Phoenix.Channel.html)s. Therefore, if your application uses both channels and LiveViews, you can use the same technique to disconnect any stateful connection. ## [](security-model.html#summing-up)Summing up The important concepts to keep in mind are: - `live_session` can be used to draw boundaries between groups of LiveViews. While you could use `live_session` to draw lines between different authorization rules, doing so would lead to frequent page reloads. For this reason, we typically use `live_session` to enforce different *authentication* requirements or whenever you need to change root layouts - Your authentication logic (logging the user in) is typically part of your regular web request pipeline and it is shared by both controllers and LiveViews. Authentication then stores the user information in the session. Regular web requests use `plug` to read the user from a session, LiveViews read it inside an `on_mount` callback. This is typically a single database lookup on both cases. Running [`mix phx.gen.auth`](../phoenix/1.7.18/Mix.Tasks.Phx.Gen.Auth.html) sets up all that is necessary - Once authenticated, your authorization logic in LiveViews will happen both during `mount` (such as "can the user see this page?") and during events (like "can the user delete this item?"). Those rules are often domain/business specific, and typically happen in your context modules. This is also a requirement for regular requests and responses [← Previous Page Live navigation](live-navigation.html) [Next Page → Telemetry](telemetry.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/security-model.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/syncing-changes.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/client/syncing-changes.md#L1 "View Source") Syncing changes and optimistic UIs When using LiveView, whenever you change the state in your LiveView process, changes are automatically sent and applied in the client. However, in many occasions, the client may have its own state: inputs, buttons, focused UI elements, and more. In order to avoid server updates from destroying state on the client, LiveView provides several features and out-of-the-box conveniences. Let's start by discussing which problems may arise from client-server integration, which may apply to any web application, and explore how LiveView solves it automatically. If you want to focus on the more practical aspects, you can jump to later sections or watch the video below: ## [](syncing-changes.html#the-problem-in-a-nutshell)The problem in a nutshell Imagine your web application has a form. The form has a single email input and a button. We have to validate that the email is unique in our database and render a tiny “✗” or “✓“ accordingly close to the input. Because we are using server-side rendering, we are debouncing/throttling form changes to the server. And, to avoid double-submissions, we want to disable the button as soon as it is clicked. Here is what could happen. The user has typed “hello@example.” and debounce kicks in, causing the client to send an event to the server. Here is how the client looks like at this moment: ``` [ hello@example. ] ------------ SUBMIT ------------ ``` While the server is processing this information, the user finishes typing the email and presses submit. The client sends the submit event to the server, then proceeds to disable the button, and change its value to “SUBMITTING”: ``` [ hello@example.com ] ------------ SUBMITTING ------------ ``` Immediately after pressing submit, the client receives an update from the server, but this is an update from the debounce event! If the client were to simply render this server update, the client would effectively roll back the form to the previous state shown below, which would be a disaster: ``` [ hello@example. ] ✓ ------------ SUBMIT ------------ ``` This is a simple example of how client and server state can evolve and differ for periods of times, due to the latency (distance) between them, in any web application, not only LiveView. LiveView solves this in two ways: - The JavaScript client is always the source of truth for current input values - LiveView tracks how many events are currently in flight in a given input/button/form. The changes to the form are applied behind the scenes as they arrive, but LiveView only shows them once all in-flight events have been resolved In other words, for the most common cases, **LiveView will automatically sync client and server state for you**. This is a huge benefit of LiveView, as many other stacks would require developers to tackle these problems themselves. For complete detail in how LiveView handles forms, see [the JavaScript client specifics in the Form Bindings page](form-bindings.html#javascript-client-specifics). ## [](syncing-changes.html#optimistic-uis-via-loading-classes)Optimistic UIs via loading classes Whenever an HTML element pushes an event to the server, LiveView will attach a `-loading` class to it. For example the following markup: ``` <button phx-click="clicked" phx-window-keydown="key">...</button> ``` On click, would receive the `phx-click-loading` class, and on keydown would receive the `phx-keydown-loading` class. The CSS loading classes are maintained until an acknowledgement is received on the client for the pushed event. If the element is triggered several times, the loading state is removed only when all events are resolved. This means the most trivial optimistic UI enhancements can be done in LiveView by simply adding a CSS rule. For example, imagine you want to fade the text of an element when it is clicked, while it waits for a response: ``` .phx-click-loading.opaque-on-click { opacity: 50%; } ``` Now, by adding the class `opaque-on-click` to any element, the elements give an immediate feedback on click. The following events receive CSS loading classes: - `phx-click` - `phx-click-loading` - `phx-change` - `phx-change-loading` - `phx-submit` - `phx-submit-loading` - `phx-focus` - `phx-focus-loading` - `phx-blur` - `phx-blur-loading` - `phx-window-keydown` - `phx-keydown-loading` - `phx-window-keyup` - `phx-keyup-loading` Events that happen inside a form have their state applied to both the element and the form. When an input changes, `phx-change-loading` applies to both input and form. On submit, both button and form get the `phx-submit-loading` classes. Buttons, in particular, also support a `phx-disabled-with` attribute, which allows you to customize the text of the button on click: ``` <button phx-disable-with="Submitting...">Submit</button> ``` ### [](syncing-changes.html#tailwind-integration)Tailwind integration If you are using Tailwind, you may want to use [the `addVariant` plugin](https://tailwindcss.com/docs/plugins#adding-variants) to make it even easier to customize your elements loading state. ``` plugins: [ plugin(({ addVariant }) => { addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &",]); addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &",]); addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &",]); }), ], ``` Now to fade one element on click, you simply need to add: ``` <button phx-click="clicked" class="phx-click-loading:opacity-50">...</button> ``` ## [](syncing-changes.html#optimistic-uis-via-js-commands)Optimistic UIs via JS commands While loading classes are extremely handy, they only apply to the element currently clicked. Sometimes, you may to click a "Delete" button but mark the whole row that holds the button as loading (for example, to fade it out). By using JS commands, you can tell LiveView which elements get the loading state: ``` <button phx-click={JS.push("delete", loading: "#post-row-13")}>Delete</button> ``` Besides custom loading elements, you can use [JS commands](Phoenix.LiveView.JS.html) for a huge variety of operations, such as adding/removing classes, toggling attributes, hiding elements, transitions, and more. For example, imagine that you want to immediately remove an element from the page on click, you can do this: ``` <button phx-click={JS.push("delete") |> JS.hide()}>Delete</button> ``` If the element you want to delete is not the clicked button, but its parent (or other element), you can pass a selector to hide: ``` <button phx-click={JS.push("delete") |> JS.hide("#post-row-13")}>Delete</button> ``` Or if you'd rather add a class instead: ``` <button phx-click={JS.push("delete") |> JS.add_class("opacity-50")}>Delete</button> ``` One key property of JS commands, such as `hide` and `add_class`, is that they are DOM-patch aware, so operations applied by the JS APIs will stick to elements across patches from the server. JS commands also include a `dispatch` function, which dispatches an event to the DOM element to trigger client-specific functionality. For example, to trigger copying to a clipboard, you may implement this event listener: ``` window.addEventListener("app:clipcopy", (event) => { if ("clipboard" in navigator) { if (event.target.tagName === "INPUT") { navigator.clipboard.writeText(event.target.value); } else { navigator.clipboard.writeText(event.target.textContent); } } else { alert( "Sorry, your browser does not support clipboard copy.\nThis generally requires a secure origin — either HTTPS or localhost.", ); } }); ``` And then trigger it as follows: ``` <button phx-click={JS.dispatch("app:clipcopy", to: "#printed-output")}>Copy</button> ``` Transitions are also only a few characters away: ``` <div id="item">My Item</div> <button phx-click={JS.transition("shake", to: "#item")}>Shake!</button> ``` See [`Phoenix.LiveView.JS`](Phoenix.LiveView.JS.html) for more examples and documentation. ## [](syncing-changes.html#optimistic-uis-via-js-hooks)Optimistic UIs via JS hooks On the most complex cases, you can assume control of a DOM element, and control exactly how and when server updates apply to the element on the page. See [the Client hooks via `phx-hook` section in the JavaScript interoperability page](js-interop.html#client-hooks-via-phx-hook) to learn more. ## [](syncing-changes.html#live-navigation)Live navigation LiveView also provides mechanisms to customize and interact with navigation events. ### [](syncing-changes.html#navigation-classes)Navigation classes The following classes are applied to the LiveView's parent container: - `"phx-connected"` - applied when the view has connected to the server - `"phx-loading"` - applied when the view is not connected to the server - `"phx-error"` - applied when an error occurs on the server. Note, this class will be applied in conjunction with `"phx-loading"` if connection to the server is lost. ### [](syncing-changes.html#navigation-events)Navigation events For live page navigation via `<.link navigate={...}>` and `<.link patch={...}>`, their server-side equivalents `push_navigate` and `push_patch`, as well as form submits via `phx-submit`, the JavaScript events `"phx:page-loading-start"` and `"phx:page-loading-stop"` are dispatched on window. This is useful for showing main page loading status, for example: ``` // app.js import topbar from "topbar" window.addEventListener("phx:page-loading-start", info => topbar.delayedShow(500)) window.addEventListener("phx:page-loading-stop", info => topbar.hide()) ``` Within the callback, `info.detail` will be an object that contains a `kind` key, with a value that depends on the triggering event: - `"redirect"` - the event was triggered by a redirect - `"patch"` - the event was triggered by a patch - `"initial"` - the event was triggered by initial page load - `"element"` - the event was triggered by a `phx-` bound element, such as `phx-click` - `"error"` - the event was triggered by an error, such as a view crash or socket disconnection Additionally, any `phx-` event may dispatch page loading events by annotating the DOM element with the `phx-page-loading` attribute. For all kinds of page loading events, all but `"element"` will receive an additional `to` key in the info metadata pointing to the href associated with the page load. In the case of an `"element"` page loading event, the info will contain a `"target"` key containing the DOM element which triggered the page loading state. A lower level `phx:navigate` event is also triggered any time the browser's URL bar is programmatically changed by Phoenix or the user navigation forward or back. The `info.detail` will contain the following information: - `"href"` - the location the URL bar was navigated to. - `"patch"` - the boolean flag indicating this was a patch navigation. - `"pop"` - the boolean flag indication this was a navigation via `popstate` from a user navigation forward or back in history. [← Previous Page JavaScript interoperability](js-interop.html) [Next Page → phx- HTML attributes](html-attrs.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/client/syncing-changes.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/telemetry.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/telemetry.md#L1 "View Source") Telemetry LiveView currently exposes the following [`telemetry`](../telemetry.html) events: - `[:phoenix, :live_view, :mount, :start]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) immediately before [`mount/3`](Phoenix.LiveView.html#c:mount/3) is invoked. - Measurement: ``` %{system_time: System.monotonic_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, params: unsigned_params | :not_mounted_at_router, session: map, uri: String.t() | nil } ``` - `[:phoenix, :live_view, :mount, :stop]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when the [`mount/3`](Phoenix.LiveView.html#c:mount/3) callback completes successfully. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, params: unsigned_params | :not_mounted_at_router, session: map, uri: String.t() | nil } ``` - `[:phoenix, :live_view, :mount, :exception]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when an exception is raised in the [`mount/3`](Phoenix.LiveView.html#c:mount/3) callback. - Measurement: `%{duration: native_time}` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, kind: atom, reason: term, params: unsigned_params | :not_mounted_at_router, session: map, uri: String.t() | nil } ``` - `[:phoenix, :live_view, :handle_params, :start]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) immediately before [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) is invoked. - Measurement: ``` %{system_time: System.monotonic_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, params: unsigned_params, uri: String.t() } ``` - `[:phoenix, :live_view, :handle_params, :stop]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when the [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) callback completes successfully. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, params: unsigned_params, uri: String.t() } ``` - `[:phoenix, :live_view, :handle_params, :exception]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when an exception is raised in the [`handle_params/3`](Phoenix.LiveView.html#c:handle_params/3) callback. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, kind: atom, reason: term, params: unsigned_params, uri: String.t() } ``` - `[:phoenix, :live_view, :handle_event, :start]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) immediately before [`handle_event/3`](Phoenix.LiveView.html#c:handle_event/3) is invoked. - Measurement: ``` %{system_time: System.monotonic_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, event: String.t(), params: unsigned_params } ``` - `[:phoenix, :live_view, :handle_event, :stop]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when the [`handle_event/3`](Phoenix.LiveView.html#c:handle_event/3) callback completes successfully. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, event: String.t(), params: unsigned_params } ``` - `[:phoenix, :live_view, :handle_event, :exception]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when an exception is raised in the [`handle_event/3`](Phoenix.LiveView.html#c:handle_event/3) callback. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, kind: atom, reason: term, event: String.t(), params: unsigned_params } ``` - `[:phoenix, :live_view, :render, :start]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) immediately before [`render/1`](Phoenix.LiveComponent.html#c:render/1) is invoked. - Measurement: ``` %{system_time: System.monotonic_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, force?: boolean, changed?: boolean } ``` - `[:phoenix, :live_view, :render, :stop]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when the [`render/1`](Phoenix.LiveView.html#c:render/1) callback completes successfully. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, force?: boolean, changed?: boolean } ``` - `[:phoenix, :live_view, :render, :exception]` - Dispatched by a [`Phoenix.LiveView`](Phoenix.LiveView.html) when an exception is raised in the [`render/1`](Phoenix.LiveView.html#c:render/1) callback. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, kind: atom, reason: term, force?: boolean, changed?: boolean } ``` - `[:phoenix, :live_component, :update, :start]` - Dispatched by a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) immediately before [`update/2`](Phoenix.LiveComponent.html#c:update/2) or a [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) is invoked. In the case of[`update/2`](Phoenix.LiveComponent.html#c:update/2) it might dispatch one event for multiple calls. - Measurement: ``` %{system_time: System.monotonic_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, component: atom, assigns_sockets: [{map(), Phoenix.LiveView.Socket.t}] } ``` - `[:phoenix, :live_component, :update, :stop]` - Dispatched by a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) when the [`update/2`](Phoenix.LiveComponent.html#c:update/2) or a [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) callback completes successfully. In the case of[`update/2`](Phoenix.LiveComponent.html#c:update/2) it might dispatch one event for multiple calls. The `sockets` metadata contain the updated sockets. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, component: atom, assigns_sockets: [{map(), Phoenix.LiveView.Socket.t}], sockets: [Phoenix.LiveView.Socket.t] } ``` - `[:phoenix, :live_component, :update, :exception]` - Dispatched by a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) when an exception is raised in the [`update/2`](Phoenix.LiveComponent.html#c:update/2) or a [`update_many/1`](Phoenix.LiveComponent.html#c:update_many/1) callback. In the case of[`update/2`](Phoenix.LiveComponent.html#c:update/2) it might dispatch one event for multiple calls. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, kind: atom, reason: term, component: atom, assigns_sockets: [{map(), Phoenix.LiveView.Socket.t}] } ``` - `[:phoenix, :live_component, :handle_event, :start]` - Dispatched by a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) immediately before [`handle_event/3`](Phoenix.LiveComponent.html#c:handle_event/3) is invoked. - Measurement: ``` %{system_time: System.monotonic_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, component: atom, event: String.t(), params: unsigned_params } ``` - `[:phoenix, :live_component, :handle_event, :stop]` - Dispatched by a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) when the [`handle_event/3`](Phoenix.LiveComponent.html#c:handle_event/3) callback completes successfully. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, component: atom, event: String.t(), params: unsigned_params } ``` - `[:phoenix, :live_component, :handle_event, :exception]` - Dispatched by a [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) when an exception is raised in the [`handle_event/3`](Phoenix.LiveComponent.html#c:handle_event/3) callback. - Measurement: ``` %{duration: native_time} ``` - Metadata: ``` %{ socket: Phoenix.LiveView.Socket.t, kind: atom, reason: term, component: atom, event: String.t(), params: unsigned_params } ``` [← Previous Page Security considerations](security-model.html) [Next Page → Uploads](uploads.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/telemetry.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/uploads.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/server/uploads.md#L1 "View Source") Uploads LiveView supports interactive file uploads with progress for both direct to server uploads as well as direct-to-cloud [external uploads](external-uploads.html) on the client. ## [](uploads.html#built-in-features)Built-in Features - Accept specification - Define accepted file types, max number of entries, max file size, etc. When the client selects file(s), the file metadata is automatically validated against the specification. See [`Phoenix.LiveView.allow_upload/3`](Phoenix.LiveView.html#allow_upload/3). - Reactive entries - Uploads are populated in an `@uploads` assign in the socket. Entries automatically respond to progress, errors, cancellation, etc. - Drag and drop - Use the `phx-drop-target` attribute to enable. See [`Phoenix.Component.live_file_input/1`](Phoenix.Component.html#live_file_input/1). ## [](uploads.html#allow-uploads)Allow uploads You enable an upload, typically on mount, via [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3). For this example, we will also keep a list of uploaded files in a new assign named `uploaded_files`, but you could name it something else if you wanted. ``` @impl Phoenix.LiveView def mount(_params, _session, socket) do {:ok, socket |> assign(:uploaded_files, []) |> allow_upload(:avatar, accept: ~w(.jpg .jpeg), max_entries: 2)} end ``` That's it for now! We will come back to the LiveView to implement some form- and upload-related callbacks later, but most of the functionality around uploads takes place in the template. ## [](uploads.html#render-reactive-elements)Render reactive elements Use the [`Phoenix.Component.live_file_input/1`](Phoenix.Component.html#live_file_input/1) component to render a file input for the upload: ``` <%!-- lib/my_app_web/live/upload_live.html.heex --%> <form id="upload-form" phx-submit="save" phx-change="validate"> <.live_file_input upload={@uploads.avatar} /> <button type="submit">Upload</button> </form> ``` > **Important:** You must bind `phx-submit` and `phx-change` on the form. Note that while [`live_file_input/1`](Phoenix.Component.html#live_file_input/1) allows you to set additional attributes on the file input, many attributes such as `id`, `accept`, and `multiple` will be set automatically based on the [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3) spec. Reactive updates to the template will occur as the end-user interacts with the file input. ### [](uploads.html#upload-entries)Upload entries Uploads are populated in an `@uploads` assign in the socket. Each allowed upload contains a *list* of entries, irrespective of the `:max_entries` value in the [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3) spec. These entry structs contain all the information about an upload, including progress, client file info, errors, etc. Let's look at an annotated example: ``` <%!-- lib/my_app_web/live/upload_live.html.heex --%> <%!-- use phx-drop-target with the upload ref to enable file drag and drop --%> <section phx-drop-target={@uploads.avatar.ref}> <%!-- render each avatar entry --%> <article :for={entry <- @uploads.avatar.entries} class="upload-entry"> <figure> <.live_img_preview entry={entry} /> <figcaption>{entry.client_name}</figcaption> </figure> <%!-- entry.progress will update automatically for in-flight entries --%> <progress value={entry.progress} max="100"> {entry.progress}% </progress> <%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%> <button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref} aria-label="cancel">&times;</button> <%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%> <p :for={err <- upload_errors(@uploads.avatar, entry)} class="alert alert-danger">{error_to_string(err)}</p> </article> <%!-- Phoenix.Component.upload_errors/1 returns a list of error atoms --%> <p :for={err <- upload_errors(@uploads.avatar)} class="alert alert-danger"> {error_to_string(err)} </p> </section> ``` The `section` element in the example acts as the `phx-drop-target` for the `:avatar` upload. Users can interact with the file input or they can drop files over the element to add new entries. Upload entries are created when a file is added to the form input and each will exist until it has been consumed, following a successfully completed upload. ### [](uploads.html#entry-validation)Entry validation Validation occurs automatically based on any conditions that were specified in [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3) however, as mentioned previously you are required to bind `phx-change` on the form in order for the validation to be performed. Therefore you must implement at least a minimal callback: ``` @impl Phoenix.LiveView def handle_event("validate", _params, socket) do {:noreply, socket} end ``` Entries for files that do not match the [`allow_upload/3`](Phoenix.LiveView.html#allow_upload/3) spec will contain errors. Use [`Phoenix.Component.upload_errors/2`](Phoenix.Component.html#upload_errors/2) and your own helper function to render a friendly error message: ``` defp error_to_string(:too_large), do: "Too large" defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type" ``` For error messages that affect all entries, use [`Phoenix.Component.upload_errors/1`](Phoenix.Component.html#upload_errors/1), and your own helper function to render a friendly error message: ``` defp error_to_string(:too_many_files), do: "You have selected too many files" ``` ### [](uploads.html#cancel-an-entry)Cancel an entry Upload entries may also be canceled, either programmatically or as a result of a user action. For instance, to handle the click event in the template above, you could do the following: ``` @impl Phoenix.LiveView def handle_event("cancel-upload", %{"ref" => ref}, socket) do {:noreply, cancel_upload(socket, :avatar, ref)} end ``` ## [](uploads.html#consume-uploaded-entries)Consume uploaded entries When the end-user submits a form containing a [`live_file_input/1`](Phoenix.Component.html#live_file_input/1), the JavaScript client first uploads the file(s) before invoking the callback for the form's `phx-submit` event. Within the callback for the `phx-submit` event, you invoke the [`Phoenix.LiveView.consume_uploaded_entries/3`](Phoenix.LiveView.html#consume_uploaded_entries/3) function to process the completed uploads, persisting the relevant upload data alongside the form data: ``` @impl Phoenix.LiveView def handle_event("save", _params, socket) do uploaded_files = consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry -> dest = Path.join(Application.app_dir(:my_app, "priv/static/uploads"), Path.basename(path)) # You will need to create `priv/static/uploads` for `File.cp!/2` to work. File.cp!(path, dest) {:ok, ~p"/uploads/#{Path.basename(dest)}"} end) {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))} end ``` > **Note**: While client metadata cannot be trusted, max file size validations are enforced as each chunk is received when performing direct to server uploads. This example writes the file directly to disk, under the `priv` folder. In order to access your upload, for example in an `<img />` tag, you need to add the `uploads` directory to `static_paths/0`. In a vanilla Phoenix project, this is found in `lib/my_app_web.ex`. Another thing to be aware of is that in development, changes to `priv/static/uploads` will be picked up by `live_reload`. This means that as soon as your upload succeeds, your app will be reloaded in the browser. This can be temporarily disabled by setting `code_reloader: false` in `config/dev.exs`. Besides the above, this approach also has limitations in production. If you are running multiple instances of your application, the uploaded file will be stored only in one of the instances. Any request routed to the other machine will ultimately fail. For these reasons, it is best if uploads are stored elsewhere, such as the database (depending on the size and contents) or a separate storage service. For more information on implementing client-side, direct-to-cloud uploads, see the [External uploads guide](external-uploads.html) for details. ## [](uploads.html#appendix-a-uploadlive)Appendix A: UploadLive A complete example of the LiveView from this guide: ``` # lib/my_app_web/live/upload_live.ex defmodule MyAppWeb.UploadLive do use MyAppWeb, :live_view @impl Phoenix.LiveView def mount(_params, _session, socket) do {:ok, socket |> assign(:uploaded_files, []) |> allow_upload(:avatar, accept: ~w(.jpg .jpeg), max_entries: 2)} end @impl Phoenix.LiveView def handle_event("validate", _params, socket) do {:noreply, socket} end @impl Phoenix.LiveView def handle_event("cancel-upload", %{"ref" => ref}, socket) do {:noreply, cancel_upload(socket, :avatar, ref)} end @impl Phoenix.LiveView def handle_event("save", _params, socket) do uploaded_files = consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry -> dest = Path.join([:code.priv_dir(:my_app), "static", "uploads", Path.basename(path)]) # You will need to create `priv/static/uploads` for `File.cp!/2` to work. File.cp!(path, dest) {:ok, ~p"/uploads/#{Path.basename(dest)}"} end) {:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))} end defp error_to_string(:too_large), do: "Too large" defp error_to_string(:too_many_files), do: "You have selected too many files" defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type" end ``` To access your uploads via your app, make sure to add `uploads` to `MyAppWeb.static_paths/0`. [← Previous Page Telemetry](telemetry.html) [Next Page → Bindings](bindings.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/server/uploads.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_live_view/welcome.html.md === [Phoenix LiveView](http://www.phoenixframework.org) v1.0.2 - GUIDES - Modules <!--THE END--> <!--THE END--> Search documentation of Phoenix LiveView Settings # [View Source](https://github.com/phoenixframework/phoenix_live_view/blob/v1.0.2/guides/introduction/welcome.md#L1 "View Source") Welcome Welcome to Phoenix LiveView documentation. Phoenix LiveView enables rich, real-time user experiences with server-rendered HTML. A general overview of LiveView and its benefits is [available in our README](https://github.com/phoenixframework/phoenix_live_view). ## [](welcome.html#what-is-a-liveview)What is a LiveView? LiveViews are processes that receive events, update their state, and render updates to a page as diffs. The LiveView programming model is declarative: instead of saying "once event X happens, change Y on the page", events in LiveView are regular messages which may cause changes to the state. Once the state changes, the LiveView will re-render the relevant parts of its HTML template and push it to the browser, which updates the page in the most efficient manner. LiveView state is nothing more than functional and immutable Elixir data structures. The events are either internal application messages (usually emitted by [`Phoenix.PubSub`](../phoenix_pubsub/2.1.3/Phoenix.PubSub.html)) or sent by the client/browser. Every LiveView is first rendered statically as part of a regular HTTP request, which provides quick times for "First Meaningful Paint", in addition to helping search and indexing engines. A persistent connection is then established between the client and server. This allows LiveView applications to react faster to user events as there is less work to be done and less data to be sent compared to stateless requests that have to authenticate, decode, load, and encode data on every request. ## [](welcome.html#example)Example LiveView is included by default in Phoenix applications. Therefore, to use LiveView, you must have already installed Phoenix and created your first application. If you haven't done so, check [Phoenix' installation guide](../phoenix/installation.html) to get started. The behaviour of a LiveView is outlined by a module which implements a series of functions as callbacks. Let's see an example. Write the file below to `lib/my_app_web/live/thermostat_live.ex`: ``` defmodule MyAppWeb.ThermostatLive do use MyAppWeb, :live_view def render(assigns) do ~H""" Current temperature: {@temperature}°F <button phx-click="inc_temperature">+</button> """ end def mount(_params, _session, socket) do temperature = 70 # Let's assume a fixed temperature for now {:ok, assign(socket, :temperature, temperature)} end def handle_event("inc_temperature", _params, socket) do {:noreply, update(socket, :temperature, &(&1 + 1))} end end ``` The module above defines three functions (they are callbacks required by LiveView). The first one is `render/1`, which receives the socket `assigns` and is responsible for returning the content to be rendered on the page. We use the `~H` sigil to define a HEEx template, which stands for HTML+EEx. They are an extension of Elixir's builtin EEx templates, with support for HTML validation, syntax-based components, smart change tracking, and more. You can learn more about the template syntax in [`Phoenix.Component.sigil_H/2`](Phoenix.Component.html#sigil_H/2) (note [`Phoenix.Component`](Phoenix.Component.html) is automatically imported when you use [`Phoenix.LiveView`](Phoenix.LiveView.html)). The data used on rendering comes from the `mount` callback. The `mount` callback is invoked when the LiveView starts. In it, you can access the request parameters, read information stored in the session (typically information which identifies who is the current user), and a socket. The socket is where we keep all state, including assigns. `mount` proceeds to assign a default temperature to the socket. Because Elixir data structures are immutable, LiveView APIs often receive the socket and return an updated socket. Then we return `{:ok, socket}` to signal that we were able to mount the LiveView successfully. After `mount`, LiveView will render the page with the values from `assigns` and send it to the client. If you look at the HTML rendered, you will notice there is a button with a `phx-click` attribute. When the button is clicked, a `"inc_temperature"` event is sent to the server, which is matched and handled by the `handle_event` callback. This callback updates the socket and returns `{:noreply, socket}` with the updated socket. `handle_*` callbacks in LiveView (and in Elixir in general) are invoked based on some action, in this case, the user clicking a button. The `{:noreply, socket}` return means there is no additional replies sent to the browser, only that a new version of the page is rendered. LiveView then computes diffs and sends them to the client. Now we are ready to render our LiveView. You can serve the LiveView directly from your router: ``` defmodule MyAppWeb.Router do use MyAppWeb, :router pipeline :browser do ... end scope "/", MyAppWeb do pipe_through :browser ... live "/thermostat", ThermostatLive end end ``` Once the LiveView is rendered, a regular HTML response is sent. In your app.js file, you should find the following: ``` import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) liveSocket.connect() ``` Now the JavaScript client will connect over WebSockets and `mount/3` will be invoked inside a spawned LiveView process. ## [](welcome.html#parameters-and-session)Parameters and session The mount callback receives three arguments: the request parameters, the session, and the socket. The parameters can be used to read information from the URL. For example, assuming you have a `Thermostat` module defined somewhere that can read this information based on the house name, you could write this: ``` def mount(%{"house" => house}, _session, socket) do temperature = Thermostat.get_house_reading(house) {:ok, assign(socket, :temperature, temperature)} end ``` And then in your router: ``` live "/thermostat/:house", ThermostatLive ``` The session retrieves information from a signed (or encrypted) cookie. This is where you can store authentication information, such as `current_user_id`: ``` def mount(_params, %{"current_user_id" => user_id}, socket) do temperature = Thermostat.get_user_reading(user_id) {:ok, assign(socket, :temperature, temperature)} end ``` > Phoenix comes with built-in authentication generators. See [`mix phx.gen.auth`](../phoenix/1.7.18/Mix.Tasks.Phx.Gen.Auth.html). Most times, in practice, you will use both: ``` def mount(%{"house" => house}, %{"current_user_id" => user_id}, socket) do temperature = Thermostat.get_house_reading(user_id, house) {:ok, assign(socket, :temperature, temperature)} end ``` In other words, you want to read the information about a given house, as long as the user has access to it. ## [](welcome.html#bindings)Bindings Phoenix supports DOM element bindings for client-server interaction. For example, to react to a click on a button, you would render the element: ``` <button phx-click="inc_temperature">+</button> ``` Then on the server, all LiveView bindings are handled with the `handle_event/3` callback, for example: ``` def handle_event("inc_temperature", _value, socket) do {:noreply, update(socket, :temperature, &(&1 + 1))} end ``` To update UI state, for example, to open and close dropdowns, switch tabs, etc, LiveView also supports JS commands ([`Phoenix.LiveView.JS`](Phoenix.LiveView.JS.html)), which execute directly on the client without reaching the server. To learn more, see [our bindings page](bindings.html) for a complete list of all LiveView bindings as well as our [JavaScript interoperability guide](js-interop.html). LiveView has built-in support for forms, including uploads and association management. See [`Phoenix.Component.form/1`](Phoenix.Component.html#form/1) as a starting point and [`Phoenix.Component.inputs_for/1`](Phoenix.Component.html#inputs_for/1) for working with associations. The [Uploads](uploads.html) and [Form bindings](form-bindings.html) guides provide more information about advanced features. ## [](welcome.html#navigation)Navigation LiveView provides functionality to allow page navigation using the [browser's pushState API](https://developer.mozilla.org/en-US/docs/Web/API/History_API). With live navigation, the page is updated without a full page reload. You can either *patch* the current LiveView, updating its URL, or *navigate* to a new LiveView. You can learn more about them in the [Live Navigation](live-navigation.html) guide. ## [](welcome.html#generators)Generators Phoenix v1.6 and later includes code generators for LiveView. If you want to see an example of how to structure your application, from the database all the way up to LiveViews, run the following: ``` $ mix phx.gen.live Blog Post posts title:string body:text ``` For more information, run [`mix help phx.gen.live`](../phoenix/1.7.18/Mix.Tasks.Phx.Gen.Live.html). For authentication, with built-in LiveView support, run `mix phx.gen.auth Account User users`. ## [](welcome.html#compartmentalize-state-markup-and-events-in-liveview)Compartmentalize state, markup, and events in LiveView LiveView supports two extension mechanisms: function components, provided by `HEEx` templates, and stateful components, known as LiveComponents. Similar to `render(assigns)` in our LiveView, a function component is any function that receives an assigns map and returns a `~H` template. For example: ``` def weather_greeting(assigns) do ~H""" <div title="My div" class={@class}> <p>Hello {@name}</p> <MyApp.Weather.city name="Kraków"/> </div> """ end ``` You can learn more about function components in the [`Phoenix.Component`](Phoenix.Component.html) module. At the end of the day, they are a useful mechanism to reuse markup in your LiveViews. However, sometimes you need to compartmentalize or reuse more than just markup. Perhaps you want to move part of the state or some of the events in your LiveView to a separate module. For these cases, LiveView provides [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html), which are rendered using [`live_component/1`](Phoenix.Component.html#live_component/1): ``` <.live_component module={UserComponent} id={user.id} user={user} /> ``` LiveComponents have their own `mount/1` and `handle_event/3` callbacks, as well as their own state with change tracking support, similar to LiveViews. They are lightweight since they "run" in the same process as the parent LiveView. However, this means an error in a LiveComponent would cause the whole view to fail to render. For a complete rundown, see [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html). Finally, if you want complete isolation between parts of a LiveView, you can always render a LiveView inside another LiveView by calling [`live_render/3`](Phoenix.Component.html#live_render/3). This child LiveView runs in a separate process than the parent, with its own callbacks. If a child LiveView crashes, it won't affect the parent. If the parent crashes, all children are terminated. When rendering a child LiveView, the `:id` option is required to uniquely identify the child. A child LiveView will only ever be rendered and mounted a single time, provided its ID remains unchanged. To force a child to re-mount with new session data, a new ID must be provided. Given that it runs in its own process, a nested LiveView is an excellent tool for creating completely isolated UI elements, but it is a slightly expensive abstraction if all you want is to compartmentalize markup or events (or both). To sum it up: - use [`Phoenix.Component`](Phoenix.Component.html) to compartmentalize/reuse markup - use [`Phoenix.LiveComponent`](Phoenix.LiveComponent.html) to compartmentalize state, markup, and events - use nested [`Phoenix.LiveView`](Phoenix.LiveView.html) to compartmentalize state, markup, events, and error isolation ## [](welcome.html#guides)Guides This documentation is split into two categories. We have the API reference for all LiveView modules, that's where you will learn more about [`Phoenix.Component`](Phoenix.Component.html), [`Phoenix.LiveView`](Phoenix.LiveView.html), and so on. LiveView also has many guides to help you on your journey, split on server-side and client-side: ### [](welcome.html#server-side)Server-side These guides focus on server-side functionality: - [Assigns and HEEx templates](assigns-eex.html) - [Deployments and recovery](deployments.html) - [Error and exception handling](error-handling.html) - [Gettext for internationalization](gettext.html) - [Live layouts](live-layouts.html) - [Live navigation](live-navigation.html) - [Security considerations](security-model.html) - [Telemetry](telemetry.html) - [Uploads](uploads.html) ### [](welcome.html#client-side)Client-side These guides focus on LiveView bindings and client-side integration: - [Bindings](bindings.html) - [External uploads](external-uploads.html) - [Form bindings](form-bindings.html) - [JavaScript interoperability](js-interop.html) - [Syncing changes and optimistic UIs](syncing-changes.html) [← Previous Page Changelog](changelog.html) [Next Page → Assigns and HEEx templates](assigns-eex.html) [Hex Package](https://hex.pm/packages/phoenix_live_view/1.0.2) [Hex Preview](https://preview.hex.pm/preview/phoenix_live_view/1.0.2) ([current file](https://preview.hex.pm/preview/phoenix_live_view/1.0.2/show/guides/introduction/welcome.md)) Search HexDocs [Download ePub version](Phoenix%20LiveView.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.36.1) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_pubsub/Phoenix.PubSub.Adapter.html.md === Search [Phoenix.PubSub](http://www.phoenixframework.org) **v2.1.3** - [Pages](Phoenix.PubSub.Adapter.html#full-list) - [Modules](Phoenix.PubSub.Adapter.html#full-list) <!--THE END--> # Settings [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/adapter.ex#L1 "View Source") Phoenix.PubSub.Adapter behaviour (Phoenix.PubSub v2.1.3) Specification to implement a custom PubSub adapter. # [Link to this section](Phoenix.PubSub.Adapter.html#summary) Summary ## [Types](Phoenix.PubSub.Adapter.html#types) [adapter\_name()](Phoenix.PubSub.Adapter.html#t:adapter_name/0) ## [Callbacks](Phoenix.PubSub.Adapter.html#callbacks) [broadcast( adapter\_name, topic, message, dispatcher )](Phoenix.PubSub.Adapter.html#c:broadcast/4) Broadcasts the given topic, message, and dispatcher to all nodes in the cluster (except the current node itself). [child\_spec(keyword)](Phoenix.PubSub.Adapter.html#c:child_spec/1) Returns a child specification that mounts the processes required for the adapter. [direct\_broadcast( adapter\_name, node\_name, topic, message, dispatcher )](Phoenix.PubSub.Adapter.html#c:direct_broadcast/5) Broadcasts the given topic, message, and dispatcher to given node in the cluster (it may point to itself). [node\_name(adapter\_name)](Phoenix.PubSub.Adapter.html#c:node_name/1) Returns the node name as an atom or a binary. # [Link to this section](Phoenix.PubSub.Adapter.html#types) Types [Link to this type](Phoenix.PubSub.Adapter.html#t:adapter_name/0 "Link to this type") # adapter\_name() [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/adapter.ex#L6 "View Source") ``` @type adapter_name() :: atom() ``` # [Link to this section](Phoenix.PubSub.Adapter.html#callbacks) Callbacks [Link to this callback](Phoenix.PubSub.Adapter.html#c:broadcast/4 "Link to this callback") # broadcast( adapter\_name, topic, message, dispatcher ) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/adapter.ex#L33 "View Source") ``` @callback broadcast( adapter_name(), topic :: Phoenix.PubSub.topic(), message :: Phoenix.PubSub.message(), dispatcher :: Phoenix.PubSub.dispatcher() ) :: :ok | {:error, term()} ``` Broadcasts the given topic, message, and dispatcher to all nodes in the cluster (except the current node itself). [Link to this callback](Phoenix.PubSub.Adapter.html#c:child_spec/1 "Link to this callback") # child\_spec(keyword) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/adapter.ex#L22 "View Source") ``` @callback child_spec(keyword()) :: Supervisor.child_spec() ``` Returns a child specification that mounts the processes required for the adapter. `child_spec` will receive all options given [`Phoenix.PubSub`](Phoenix.PubSub.html). Note, however, that the `:name` under options is the name of the complete PubSub system. The reserved key space to be used by the adapter is under the `:adapter_name` key. [Link to this callback](Phoenix.PubSub.Adapter.html#c:direct_broadcast/5 "Link to this callback") # direct\_broadcast( adapter\_name, node\_name, topic, message, dispatcher ) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/adapter.ex#L45 "View Source") ``` @callback direct_broadcast( adapter_name(), node_name :: Phoenix.PubSub.node_name(), topic :: Phoenix.PubSub.topic(), message :: Phoenix.PubSub.message(), dispatcher :: Phoenix.PubSub.dispatcher() ) :: :ok | {:error, term()} ``` Broadcasts the given topic, message, and dispatcher to given node in the cluster (it may point to itself). [Link to this callback](Phoenix.PubSub.Adapter.html#c:node_name/1 "Link to this callback") # node\_name(adapter\_name) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/adapter.ex#L11 "View Source") ``` @callback node_name(adapter_name()) :: Phoenix.PubSub.node_name() ``` Returns the node name as an atom or a binary. On Hex.pm: [Package](https://hex.pm/packages/phoenix_pubsub/2.1.3) [Preview](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3) [(current file)](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3/show/lib/phoenix/pubsub/adapter.ex) Search Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.28.3) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_pubsub/Phoenix.PubSub.BroadcastError.html.md === Search [Phoenix.PubSub](http://www.phoenixframework.org) **v2.1.3** - [Pages](Phoenix.PubSub.BroadcastError.html#full-list) - [Modules](Phoenix.PubSub.BroadcastError.html#full-list) <!--THE END--> # Settings [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L70 "View Source") Phoenix.PubSub.BroadcastError exception (Phoenix.PubSub v2.1.3) On Hex.pm: [Package](https://hex.pm/packages/phoenix_pubsub/2.1.3) [Preview](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3) [(current file)](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3/show/lib/phoenix/pubsub.ex) Search Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.28.3) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_pubsub/Phoenix.PubSub.PG2.html.md === Search [Phoenix.PubSub](http://www.phoenixframework.org) **v2.1.3** - [Pages](Phoenix.PubSub.PG2.html#full-list) - [Modules](Phoenix.PubSub.PG2.html#full-list) <!--THE END--> # Settings [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/pg2.ex#L1 "View Source") Phoenix.PubSub.PG2 (Phoenix.PubSub v2.1.3) Phoenix PubSub adapter based on `:pg`/`:pg2`. It runs on Distributed Erlang and is the default adapter. # [Link to this section](Phoenix.PubSub.PG2.html#summary) Summary ## [Functions](Phoenix.PubSub.PG2.html#functions) [child\_spec(init\_arg)](Phoenix.PubSub.PG2.html#child_spec/1) Returns a specification to start this module under a supervisor. # [Link to this section](Phoenix.PubSub.PG2.html#functions) Functions [Link to this function](Phoenix.PubSub.PG2.html#child_spec/1 "Link to this function") # child\_spec(init\_arg) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub/pg2.ex#L9 "View Source") Returns a specification to start this module under a supervisor. See [`Supervisor`](../elixir/Supervisor.html). On Hex.pm: [Package](https://hex.pm/packages/phoenix_pubsub/2.1.3) [Preview](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3) [(current file)](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3/show/lib/phoenix/pubsub/pg2.ex) Search Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.28.3) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_pubsub/Phoenix.PubSub.html.md === Search [Phoenix.PubSub](http://www.phoenixframework.org) **v2.1.3** - [Pages](Phoenix.PubSub.html#full-list) - [Modules](Phoenix.PubSub.html#full-list) <!--THE END--> # Settings [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L1 "View Source") Phoenix.PubSub (Phoenix.PubSub v2.1.3) Realtime Publisher/Subscriber service. ## [getting-started](Phoenix.PubSub.html#module-getting-started) Getting started You start Phoenix.PubSub directly in your supervision tree: ``` {Phoenix.PubSub, name: :my_pubsub} ``` You can now use the functions in this module to subscribe and broadcast messages: ``` iex> alias Phoenix.PubSub iex> PubSub.subscribe(:my_pubsub, "user:123") :ok iex> Process.info(self(), :messages) {:messages, []} iex> PubSub.broadcast(:my_pubsub, "user:123", {:user_update, %{id: 123, name: "Shane"}}) :ok iex> Process.info(self(), :messages) {:messages, [{:user_update, %{id: 123, name: "Shane"}}]} ``` ## [adapters](Phoenix.PubSub.html#module-adapters) Adapters Phoenix PubSub was designed to be flexible and support multiple backends. There are two officially supported backends: - [`Phoenix.PubSub.PG2`](Phoenix.PubSub.PG2.html) - the default adapter that ships as part of Phoenix.PubSub. It uses Distributed Elixir, directly exchanging notifications between servers. It supports a `:pool_size` option to be given alongside the name, defaults to `1`. Note the `:pool_size` must be the same throughout the cluster, therefore don't configure the pool size based on `System.schedulers_online/1`, especially if you are using machines with different specs. - `Phoenix.PubSub.Redis` - uses Redis to exchange data between servers. It requires the `:phoenix_pubsub_redis` dependency. See [`Phoenix.PubSub.Adapter`](Phoenix.PubSub.Adapter.html) to implement a custom adapter. ## [custom-dispatching](Phoenix.PubSub.html#module-custom-dispatching) Custom dispatching Phoenix.PubSub allows developers to perform custom dispatching by passing a `dispatcher` module which is responsible for local message deliveries. The dispatcher must be available on all nodes running the PubSub system. The `dispatch/3` function of the given module will be invoked with the subscriptions entries, the broadcaster identifier (either a pid or `:none`), and the message to broadcast. You may want to use the dispatcher to perform special delivery for certain subscriptions. This can be done by passing the :metadata option during subscriptions. For instance, Phoenix Channels use a custom `value` to provide "fastlaning", allowing messages broadcast to thousands or even millions of users to be encoded once and written directly to sockets instead of being encoded per channel. # [Link to this section](Phoenix.PubSub.html#summary) Summary ## [Types](Phoenix.PubSub.html#types) [dispatcher()](Phoenix.PubSub.html#t:dispatcher/0) [message()](Phoenix.PubSub.html#t:message/0) [node\_name()](Phoenix.PubSub.html#t:node_name/0) [t()](Phoenix.PubSub.html#t:t/0) [topic()](Phoenix.PubSub.html#t:topic/0) ## [Functions](Phoenix.PubSub.html#functions) [broadcast!(pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#broadcast!/4) Raising version of [`broadcast/4`](Phoenix.PubSub.html#broadcast/4). [broadcast(pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#broadcast/4) Broadcasts message on given topic across the whole cluster. [broadcast\_from!(pubsub, from, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#broadcast_from!/5) Raising version of [`broadcast_from/5`](Phoenix.PubSub.html#broadcast_from/5). [broadcast\_from(pubsub, from, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#broadcast_from/5) Broadcasts message on given topic from the given process across the whole cluster. [child\_spec(options)](Phoenix.PubSub.html#child_spec/1) Returns a child specification for pubsub with the given `options`. [direct\_broadcast!(node\_name, pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#direct_broadcast!/5) Raising version of [`direct_broadcast/5`](Phoenix.PubSub.html#direct_broadcast/5). [direct\_broadcast(node\_name, pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#direct_broadcast/5) Broadcasts message on given topic to a given node. [local\_broadcast(pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#local_broadcast/4) Broadcasts message on given topic only for the current node. [local\_broadcast\_from(pubsub, from, topic, message, dispatcher \\\\ \_\_MODULE\_\_)](Phoenix.PubSub.html#local_broadcast_from/5) Broadcasts message on given topic from a given process only for the current node. [node\_name(pubsub)](Phoenix.PubSub.html#node_name/1) Returns the node name of the PubSub server. [subscribe(pubsub, topic, opts \\\\ \[\])](Phoenix.PubSub.html#subscribe/3) Subscribes the caller to the PubSub adapter's topic. [unsubscribe(pubsub, topic)](Phoenix.PubSub.html#unsubscribe/2) Unsubscribes the caller from the PubSub adapter's topic. # [Link to this section](Phoenix.PubSub.html#types) Types [Link to this type](Phoenix.PubSub.html#t:dispatcher/0 "Link to this type") # dispatcher() [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L68 "View Source") ``` @type dispatcher() :: module() ``` [Link to this type](Phoenix.PubSub.html#t:message/0 "Link to this type") # message() [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L67 "View Source") ``` @type message() :: term() ``` [Link to this type](Phoenix.PubSub.html#t:node_name/0 "Link to this type") # node\_name() [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L64 "View Source") ``` @type node_name() :: atom() | binary() ``` [Link to this type](Phoenix.PubSub.html#t:t/0 "Link to this type") # t() [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L65 "View Source") ``` @type t() :: atom() ``` [Link to this type](Phoenix.PubSub.html#t:topic/0 "Link to this type") # topic() [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L66 "View Source") ``` @type topic() :: binary() ``` # [Link to this section](Phoenix.PubSub.html#functions) Functions [Link to this function](Phoenix.PubSub.html#broadcast!/4 "Link to this function") # broadcast!(pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L240 "View Source") ``` @spec broadcast!(t(), topic(), message(), dispatcher()) :: :ok ``` Raising version of [`broadcast/4`](Phoenix.PubSub.html#broadcast/4). [Link to this function](Phoenix.PubSub.html#broadcast/4 "Link to this function") # broadcast(pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L146 "View Source") ``` @spec broadcast(t(), topic(), message(), dispatcher()) :: :ok | {:error, term()} ``` Broadcasts message on given topic across the whole cluster. - `pubsub` - The name of the pubsub system - `topic` - The topic to broadcast to, ie: `"users:123"` - `message` - The payload of the broadcast A custom dispatcher may also be given as a fourth, optional argument. See the "Custom dispatching" section in the module documentation. [Link to this function](Phoenix.PubSub.html#broadcast_from!/5 "Link to this function") # broadcast\_from!(pubsub, from, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L251 "View Source") ``` @spec broadcast_from!(t(), pid(), topic(), message(), dispatcher()) :: :ok ``` Raising version of [`broadcast_from/5`](Phoenix.PubSub.html#broadcast_from/5). [Link to this function](Phoenix.PubSub.html#broadcast_from/5 "Link to this function") # broadcast\_from(pubsub, from, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L170 "View Source") ``` @spec broadcast_from(t(), pid(), topic(), message(), dispatcher()) :: :ok | {:error, term()} ``` Broadcasts message on given topic from the given process across the whole cluster. - `pubsub` - The name of the pubsub system - `from` - The pid that will send the message - `topic` - The topic to broadcast to, ie: `"users:123"` - `message` - The payload of the broadcast The default dispatcher will broadcast the message to all subscribers except for the process that initiated the broadcast. A custom dispatcher may also be given as a fifth, optional argument. See the "Custom dispatching" section in the module documentation. [Link to this function](Phoenix.PubSub.html#child_spec/1 "Link to this function") # child\_spec(options) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L93 "View Source") ``` @spec child_spec(keyword()) :: Supervisor.child_spec() ``` Returns a child specification for pubsub with the given `options`. The `:name` is required as part of `options`. The remaining options are described below. ## [options](Phoenix.PubSub.html#child_spec/1-options) Options - `:name` - the name of the pubsub to be started - `:adapter` - the adapter to use (defaults to [`Phoenix.PubSub.PG2`](Phoenix.PubSub.PG2.html)) - `:pool_size` - number of pubsub partitions to launch (defaults to one partition for every 4 cores) [Link to this function](Phoenix.PubSub.html#direct_broadcast!/5 "Link to this function") # direct\_broadcast!(node\_name, pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L262 "View Source") ``` @spec direct_broadcast!(node_name(), t(), topic(), message(), dispatcher()) :: :ok ``` Raising version of [`direct_broadcast/5`](Phoenix.PubSub.html#direct_broadcast/5). [Link to this function](Phoenix.PubSub.html#direct_broadcast/5 "Link to this function") # direct\_broadcast(node\_name, pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L230 "View Source") Broadcasts message on given topic to a given node. - `node_name` - The target node name - `pubsub` - The name of the pubsub system - `topic` - The topic to broadcast to, ie: `"users:123"` - `message` - The payload of the broadcast **DO NOT** use this function if you wish to broadcast to the current node, as it is always serialized, use [`local_broadcast/4`](Phoenix.PubSub.html#local_broadcast/4) instead. A custom dispatcher may also be given as a fifth, optional argument. See the "Custom dispatching" section in the module documentation. [Link to this function](Phoenix.PubSub.html#local_broadcast/4 "Link to this function") # local\_broadcast(pubsub, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L190 "View Source") ``` @spec local_broadcast(t(), topic(), message(), dispatcher()) :: :ok ``` Broadcasts message on given topic only for the current node. - `pubsub` - The name of the pubsub system - `topic` - The topic to broadcast to, ie: `"users:123"` - `message` - The payload of the broadcast A custom dispatcher may also be given as a fourth, optional argument. See the "Custom dispatching" section in the module documentation. [Link to this function](Phoenix.PubSub.html#local_broadcast_from/5 "Link to this function") # local\_broadcast\_from(pubsub, from, topic, message, dispatcher \\\\ \_\_MODULE\_\_) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L210 "View Source") ``` @spec local_broadcast_from(t(), pid(), topic(), message(), dispatcher()) :: :ok ``` Broadcasts message on given topic from a given process only for the current node. - `pubsub` - The name of the pubsub system - `from` - The pid that will send the message - `topic` - The topic to broadcast to, ie: `"users:123"` - `message` - The payload of the broadcast The default dispatcher will broadcast the message to all subscribers except for the process that initiated the broadcast. A custom dispatcher may also be given as a fifth, optional argument. See the "Custom dispatching" section in the module documentation. [Link to this function](Phoenix.PubSub.html#node_name/1 "Link to this function") # node\_name(pubsub) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L273 "View Source") ``` @spec node_name(t()) :: node_name() ``` Returns the node name of the PubSub server. [Link to this function](Phoenix.PubSub.html#subscribe/3 "Link to this function") # subscribe(pubsub, topic, opts \\\\ \[]) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L119 "View Source") ``` @spec subscribe(t(), topic(), keyword()) :: :ok | {:error, term()} ``` Subscribes the caller to the PubSub adapter's topic. - `pubsub` - The name of the pubsub system - `topic` - The topic to subscribe to, for example: `"users:123"` - `opts` - The optional list of options. See below. ## [duplicate-subscriptions](Phoenix.PubSub.html#subscribe/3-duplicate-subscriptions) Duplicate Subscriptions Callers should only subscribe to a given topic a single time. Duplicate subscriptions for a Pid/topic pair are allowed and will cause duplicate events to be sent; however, when using [`Phoenix.PubSub.unsubscribe/2`](Phoenix.PubSub.html#unsubscribe/2), all duplicate subscriptions will be dropped. ## [options](Phoenix.PubSub.html#subscribe/3-options) Options - `:metadata` - provides metadata to be attached to this subscription. The metadata can be used by custom dispatching mechanisms. See the "Custom dispatching" section in the module documentation [Link to this function](Phoenix.PubSub.html#unsubscribe/2 "Link to this function") # unsubscribe(pubsub, topic) [View Source](https://github.com/phoenixframework/phoenix_pubsub/blob/v2.1.3/lib/phoenix/pubsub.ex#L131 "View Source") ``` @spec unsubscribe(t(), topic()) :: :ok ``` Unsubscribes the caller from the PubSub adapter's topic. On Hex.pm: [Package](https://hex.pm/packages/phoenix_pubsub/2.1.3) [Preview](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3) [(current file)](https://preview.hex.pm/preview/phoenix_pubsub/2.1.3/show/lib/phoenix/pubsub.ex) Search Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.28.3) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_pubsub_redis/Phoenix.PubSub.Redis.html.md === [phoenix\_pubsub\_redis](Phoenix.PubSub.Redis.html) **v3.0.1** - [Pages](Phoenix.PubSub.Redis.html#full-list) - [Modules](Phoenix.PubSub.Redis.html#full-list) <!--THE END--> # Phoenix.PubSub.Redis (phoenix\_pubsub\_redis v3.0.1) [View Source](https://github.com/phoenixframework/phoenix_pubsub_redis/blob/v3.0.1/lib/phoenix_pubsub_redis/redis.ex#L1 "View Source") Phoenix PubSub adapter based on Redis. To start it, list it in your supervision tree as: ``` {Phoenix.PubSub, adapter: Phoenix.PubSub.Redis, host: "192.168.1.100", node_name: System.get_env("NODE")} ``` You will also need to add `:phoenix_pubsub_redis` to your deps: ``` defp deps do [{:phoenix_pubsub_redis, "~> 2.1.0"}] end ``` ## [](Phoenix.PubSub.Redis.html#module-options)Options - `:url` - The url to the redis server ie: `redis://username:password@host:port` - `:name` - The required name to register the PubSub processes, ie: `MyApp.PubSub` - `:node_name` - The required name of the node, defaults to Erlang --sname flag. It must be unique. - `:host` - The redis-server host IP, defaults `"127.0.0.1"` - `:port` - The redis-server port, defaults `6379` - `:password` - The redis-server password, defaults `""` - `:ssl` - The redis-server ssl option, defaults `false` - `:redis_pool_size` - The size of the redis connection pool. Defaults `5` - `:compression_level` - Compression level applied to serialized terms - from `0` (no compression), to `9` (highest). Defaults `0` - `:socket_opts` - List of options that are passed to the network layer when connecting to the Redis server. Default `[]` - `:sentinel` - Redix sentinel configuration. Default to `nil` # [Link to this section](Phoenix.PubSub.Redis.html#summary) Summary ## [Functions](Phoenix.PubSub.Redis.html#functions) [child\_spec(init\_arg)](Phoenix.PubSub.Redis.html#child_spec/1) Returns a specification to start this module under a supervisor. # [Link to this section](Phoenix.PubSub.Redis.html#functions) Functions [Link to this function](Phoenix.PubSub.Redis.html#child_spec/1 "Link to this function") # child\_spec(init\_arg) [View Source](https://github.com/phoenixframework/phoenix_pubsub_redis/blob/v3.0.1/lib/phoenix_pubsub_redis/redis.ex#L34 "View Source") Returns a specification to start this module under a supervisor. See [`Supervisor`](../elixir/Supervisor.html). Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.24.2) for the [Elixir programming language](https://elixir-lang.org "Elixir"). Designed by [Friedel Ziegelmayer](https://twitter.com/dignifiedquire "@dignifiedquire"). [API Reference](api-reference.html "API reference") Display keyboard shortcuts Toggle night mode Go to a HexDocs package Disable tooltips Enable tooltips === phx-docs/phoenix_pubsub_redis/api-reference.html.md === [phoenix\_pubsub\_redis](Phoenix.PubSub.Redis.html) **v3.0.1** - [Pages](api-reference.html#full-list) - [Modules](api-reference.html#full-list) <!--THE END--> # API Reference phoenix\_pubsub\_redis v3.0.1 ## [](api-reference.html#modules)Modules [Phoenix.PubSub.Redis](Phoenix.PubSub.Redis.html) Phoenix PubSub adapter based on Redis. Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.24.2) for the [Elixir programming language](https://elixir-lang.org "Elixir"). Designed by [Friedel Ziegelmayer](https://twitter.com/dignifiedquire "@dignifiedquire"). [API Reference](api-reference.html "API reference") Display keyboard shortcuts Toggle night mode Go to a HexDocs package Disable tooltips Enable tooltips === phx-docs/phoenix_template/1.0.4/Phoenix.Template.EExEngine.html.md === [phoenix\_template](Phoenix.Template.html) v1.0.4 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of phoenix\_template Settings # [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template/eex_engine.ex#L1 "View Source") Phoenix.Template.EExEngine (phoenix\_template v1.0.4) The Phoenix engine that handles the `.eex` extension. # [](Phoenix.Template.EExEngine.html#summary)Summary ## [Functions](Phoenix.Template.EExEngine.html#functions) [compile(path, name)](Phoenix.Template.EExEngine.html#compile/2) Callback implementation for [`Phoenix.Template.Engine.compile/2`](Phoenix.Template.Engine.html#c:compile/2). # [](Phoenix.Template.EExEngine.html#functions)Functions [Link to this function](Phoenix.Template.EExEngine.html#compile/2 "Link to this function") # compile(path, name) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template/eex_engine.ex#L8 "View Source") Callback implementation for [`Phoenix.Template.Engine.compile/2`](Phoenix.Template.Engine.html#c:compile/2). [Hex Package](https://hex.pm/packages/phoenix_template/1.0.4) [Hex Preview](https://preview.hex.pm/preview/phoenix_template/1.0.4) ([current file](https://preview.hex.pm/preview/phoenix_template/1.0.4/show/lib/phoenix/template/eex_engine.ex)) Search HexDocs [Download ePub version](phoenix_template.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.31.0) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_template/1.0.4/Phoenix.Template.Engine.html.md === [phoenix\_template](Phoenix.Template.html) v1.0.4 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of phoenix\_template Settings # [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template/engine.ex#L1 "View Source") Phoenix.Template.Engine behaviour (phoenix\_template v1.0.4) Specifies the API for adding custom template engines into Phoenix. Engines must implement the `compile/2` function, that receives the template file and the template name (usually used as the function name of the template) and outputs the template quoted expression: ``` def compile(template_path, template_name) ``` See [`Phoenix.Template.EExEngine`](Phoenix.Template.EExEngine.html) for an example engine implementation. # [](Phoenix.Template.Engine.html#summary)Summary ## [Callbacks](Phoenix.Template.Engine.html#callbacks) [compile(template\_path, template\_name)](Phoenix.Template.Engine.html#c:compile/2) # [](Phoenix.Template.Engine.html#callbacks)Callbacks [Link to this callback](Phoenix.Template.Engine.html#c:compile/2 "Link to this callback") # compile(template\_path, template\_name) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template/engine.ex#L14 "View Source") ``` @callback compile(template_path :: binary(), template_name :: binary()) :: Macro.t() ``` [Hex Package](https://hex.pm/packages/phoenix_template/1.0.4) [Hex Preview](https://preview.hex.pm/preview/phoenix_template/1.0.4) ([current file](https://preview.hex.pm/preview/phoenix_template/1.0.4/show/lib/phoenix/template/engine.ex)) Search HexDocs [Download ePub version](phoenix_template.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.31.0) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix_template/1.0.4/Phoenix.Template.html.md === [phoenix\_template](Phoenix.Template.html) v1.0.4 - Pages - Modules <!--THE END--> <!--THE END--> Search documentation of phoenix\_template Settings # [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L1 "View Source") Phoenix.Template (phoenix\_template v1.0.4) Templates are markup languages that are compiled to Elixir code. This module provides functions for loading and compiling templates from disk. A markup language is compiled to Elixir code via an engine. See [`Phoenix.Template.Engine`](Phoenix.Template.Engine.html). In practice, developers rarely use [`Phoenix.Template`](Phoenix.Template.html#content) directly. Instead, libraries such as `Phoenix.View` and `Phoenix.LiveView` use it as a building block. ## [](Phoenix.Template.html#module-custom-template-engines)Custom Template Engines Phoenix supports custom template engines. Engines tell Phoenix how to convert a template path into quoted expressions. See [`Phoenix.Template.Engine`](Phoenix.Template.Engine.html) for more information on the API required to be implemented by custom engines. Once a template engine is defined, you can tell Phoenix about it via the template engines option: ``` config :phoenix, :template_engines, eex: Phoenix.Template.EExEngine, exs: Phoenix.Template.ExsEngine ``` ## [](Phoenix.Template.html#module-format-encoders)Format encoders Besides template engines, Phoenix has the concept of format encoders. Format encoders work per format and are responsible for encoding a given format to a string. For example, when rendering JSON, your templates may return a regular Elixir map. Then the JSON format encoder is invoked to convert it to JSON. A format encoder must export a function called `encode_to_iodata!/1` which receives the rendering artifact and returns iodata. New encoders can be added via the format encoder option: ``` config :phoenix_template, :format_encoders, html: Phoenix.HTML.Engine ``` # [](Phoenix.Template.html#summary)Summary ## [Types](Phoenix.Template.html#types) [path()](Phoenix.Template.html#t:path/0) [root()](Phoenix.Template.html#t:root/0) ## [Functions](Phoenix.Template.html#functions) [\_\_using\_\_(opts)](Phoenix.Template.html#__using__/1) Ensure `__mix_recompile__?/0` will be defined. [compile\_all(converter, root, pattern \\\\ "\*", engines \\\\ nil)](Phoenix.Template.html#compile_all/4) Compiles a function for each template in the given `root`. [embed\_templates(pattern, opts \\\\ \[\])](Phoenix.Template.html#embed_templates/2) A convenience macro for embeding templates as functions. [engines()](Phoenix.Template.html#engines/0) Returns a keyword list with all template engines extensions followed by their modules. [find\_all(root, pattern \\\\ "\*", engines \\\\ engines())](Phoenix.Template.html#find_all/3) Returns all template paths in a given template root. [format\_encoder(format)](Phoenix.Template.html#format_encoder/1) Returns the format encoder for the given template. [hash(root, pattern \\\\ "\*", engines \\\\ engines())](Phoenix.Template.html#hash/3) Returns the hash of all template paths in the given root. [module\_to\_template\_root(module, base, suffix)](Phoenix.Template.html#module_to_template_root/3) deprecated [render(module, template, format, assigns)](Phoenix.Template.html#render/4) Renders template from module. [render\_to\_iodata(module, template, format, assign)](Phoenix.Template.html#render_to_iodata/4) Renders the template and returns iodata. [render\_to\_string(module, template, format, assign)](Phoenix.Template.html#render_to_string/4) Renders the template to string. [template\_path\_to\_name(path, root)](Phoenix.Template.html#template_path_to_name/2) deprecated # [](Phoenix.Template.html#types)Types [Link to this type](Phoenix.Template.html#t:path/0 "Link to this type") # path() [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L45 "View Source") ``` @type path() :: binary() ``` [Link to this type](Phoenix.Template.html#t:root/0 "Link to this type") # root() [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L46 "View Source") ``` @type root() :: binary() ``` # [](Phoenix.Template.html#functions)Functions [Link to this macro](Phoenix.Template.html#__using__/1 "Link to this macro") # \_\_using\_\_(opts) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L53 "View Source") (macro) Ensure `__mix_recompile__?/0` will be defined. [Link to this macro](Phoenix.Template.html#compile_all/4 "Link to this macro") # compile\_all(converter, root, pattern \\\\ "\*", engines \\\\ nil) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L391 "View Source") (macro) Compiles a function for each template in the given `root`. `converter` is an anonymous function that receives the template path and returns the function name (as a string). For example, to compile all `.eex` templates in a given directory, you might do: ``` Phoenix.Template.compile_all( &(&1 |> Path.basename() |> Path.rootname(".eex")), __DIR__, "*.eex" ) ``` If the directory has templates named `foo.eex` and `bar.eex`, they will be compiled into the functions `foo/1` and `bar/1` that receive the template `assigns` as argument. You may optionally pass a keyword list of engines. If a list is given, we will lookup and compile only this subset of engines. If none is passed (`nil`), the default list returned by [`engines/0`](Phoenix.Template.html#engines/0) is used. [Link to this macro](Phoenix.Template.html#embed_templates/2 "Link to this macro") # embed\_templates(pattern, opts \\\\ \[]) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L102 "View Source") (macro) A convenience macro for embeding templates as functions. This macro is built on top of the more general [`compile_all/3`](Phoenix.Template.html#compile_all/3) functionality. ## [](Phoenix.Template.html#embed_templates/2-options)Options - `:root` - The root directory to embed files. Defaults to the current module's directory (`__DIR__`) - `:suffix` - The string value to append to embedded function names. By default, function names will be the name of the template file excluding the format and engine. A wildcard pattern may be used to select all files within a directory tree. For example, imagine a directory listing: ``` ├── pages │ ├── about.html.heex │ └── sitemap.xml.eex ``` Then to embed the templates in your module: ``` defmodule MyAppWeb.Renderer do import Phoenix.Template, only: [embed_templates: 1] embed_templates "pages/*" end ``` Now, your module will have a `about/1` and `sitemap/1` functions. Note that functions across different formats were embedded. In case you want to distinguish between them, you can give a more specific pattern: ``` defmodule MyAppWeb.Emails do import Phoenix.Template, only: [embed_templates: 2] embed_templates "pages/*.html", suffix: "_html" embed_templates "pages/*.xml", suffix: "_xml" end ``` Now the functions will be `about_html` and `sitemap_xml`. [Link to this function](Phoenix.Template.html#engines/0 "Link to this function") # engines() [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L295 "View Source") ``` @spec engines() :: %{required(atom()) => module()} ``` Returns a keyword list with all template engines extensions followed by their modules. [Link to this function](Phoenix.Template.html#find_all/3 "Link to this function") # find\_all(root, pattern \\\\ "\*", engines \\\\ engines()) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L347 "View Source") ``` @spec find_all(root(), pattern :: String.t(), %{required(atom()) => module()}) :: [ path() ] ``` Returns all template paths in a given template root. [Link to this function](Phoenix.Template.html#format_encoder/1 "Link to this function") # format\_encoder(format) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L259 "View Source") ``` @spec format_encoder(format :: String.t()) :: module() | nil ``` Returns the format encoder for the given template. [Link to this function](Phoenix.Template.html#hash/3 "Link to this function") # hash(root, pattern \\\\ "\*", engines \\\\ engines()) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L361 "View Source") ``` @spec hash(root(), pattern :: String.t(), %{required(atom()) => module()}) :: binary() ``` Returns the hash of all template paths in the given root. Used by Phoenix to check if a given root path requires recompilation. [Link to this function](Phoenix.Template.html#module_to_template_root/3 "Link to this function") # module\_to\_template\_root(module, base, suffix) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L487 "View Source") This function is deprecated. Use Phoenix.View.module\_to\_template\_root/3. [Link to this function](Phoenix.Template.html#render/4 "Link to this function") # render(module, template, format, assigns) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L184 "View Source") Renders template from module. For a module called `MyApp.FooHTML` and template "index.html.heex", it will: - First attempt to call `MyApp.FooHTML.index(assigns)` - Then fallback to `MyApp.FooHTML.render("index.html", assigns)` - Raise otherwise It expects the HTML module, the template as a string, the format, and a set of assigns. Notice that this function returns the inner representation of a template. If you want the encoded template as a result, use [`render_to_iodata/4`](Phoenix.Template.html#render_to_iodata/4) instead. ## [](Phoenix.Template.html#render/4-examples)Examples ``` Phoenix.Template.render(YourApp.UserView, "index", "html", name: "John Doe") #=> {:safe, "Hello John Doe"} ``` ## [](Phoenix.Template.html#render/4-assigns)Assigns Assigns are meant to be user data that will be available in templates. However, there are keys under assigns that are specially handled by Phoenix, they are: - `:layout` - tells Phoenix to wrap the rendered result in the given layout. See next section ## [](Phoenix.Template.html#render/4-layouts)Layouts Templates can be rendered within other templates using the `:layout` option. `:layout` accepts a tuple of the form `{LayoutModule, "template.extension"}`. To template that goes inside the layout will be placed in the `@inner_content` assign: ``` <%= @inner_content %> ``` [Link to this function](Phoenix.Template.html#render_to_iodata/4 "Link to this function") # render\_to\_iodata(module, template, format, assign) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L124 "View Source") Renders the template and returns iodata. [Link to this function](Phoenix.Template.html#render_to_string/4 "Link to this function") # render\_to\_string(module, template, format, assign) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L133 "View Source") Renders the template to string. [Link to this function](Phoenix.Template.html#template_path_to_name/2 "Link to this function") # template\_path\_to\_name(path, root) [View Source](https://github.com/phoenixframework/phoenix_template/blob/v1.0.4/lib/phoenix/template.ex#L480 "View Source") This function is deprecated. Use Phoenix.View.template\_path\_to\_name/3. [Hex Package](https://hex.pm/packages/phoenix_template/1.0.4) [Hex Preview](https://preview.hex.pm/preview/phoenix_template/1.0.4) ([current file](https://preview.hex.pm/preview/phoenix_template/1.0.4/show/lib/phoenix/template.ex)) Search HexDocs [Download ePub version](phoenix_template.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.31.0) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phx_new/1.7.16/Mix.Tasks.Phx.New.html.md === [phx\_new](https://www.phoenixframework.org) v1.7.16 - Pages - Mix Tasks <!--THE END--> <!--THE END--> Search documentation of phx\_new Settings # [View Source](https://github.com/phoenixframework/phoenix/blob/v1.7.16/installer/lib/mix/tasks/phx.new.ex#L1 "View Source") mix phx.new (phx\_new v1.7.16) Creates a new Phoenix project. It expects the path of the project as an argument. ``` $ mix phx.new PATH [--module MODULE] [--app APP] ``` A project at the given PATH will be created. The application name and module name will be retrieved from the path, unless `--module` or `--app` is given. ## [](Mix.Tasks.Phx.New.html#module-options)Options - `--umbrella` - generate an umbrella project, with one application for your domain, and a second application for the web interface. - `--app` - the name of the OTP application - `--module` - the name of the base module in the generated skeleton - `--database` - specify the database adapter for Ecto. One of: - `postgres` - via [https://github.com/elixir-ecto/postgrex](https://github.com/elixir-ecto/postgrex) - `mysql` - via [https://github.com/elixir-ecto/myxql](https://github.com/elixir-ecto/myxql) - `mssql` - via [https://github.com/livehelpnow/tds](https://github.com/livehelpnow/tds) - `sqlite3` - via [https://github.com/elixir-sqlite/ecto\_sqlite3](https://github.com/elixir-sqlite/ecto_sqlite3) Please check the driver docs for more information and requirements. Defaults to "postgres". - `--adapter` - specify the http adapter. One of: - `cowboy` - via [https://github.com/elixir-plug/plug\_cowboy](https://github.com/elixir-plug/plug_cowboy) - `bandit` - via [https://github.com/mtrudel/bandit](https://github.com/mtrudel/bandit) Please check the adapter docs for more information and requirements. Defaults to "bandit". - `--no-assets` - equivalent to `--no-esbuild` and `--no-tailwind` - `--no-dashboard` - do not include Phoenix.LiveDashboard - `--no-ecto` - do not generate Ecto files - `--no-esbuild` - do not include esbuild dependencies and assets. We do not recommend setting this option, unless for API only applications, as doing so requires you to manually add and track JavaScript dependencies - `--no-gettext` - do not generate gettext files - `--no-html` - do not generate HTML views - `--no-live` - comment out LiveView socket setup in your Endpoint and assets/js/app.js. Automatically disabled if --no-html is given - `--no-mailer` - do not generate Swoosh mailer files - `--no-tailwind` - do not include tailwind dependencies and assets. The generated markup will still include Tailwind CSS classes, those are left-in as reference for the subsequent styling of your layout and components - `--binary-id` - use `binary_id` as primary key type in Ecto schemas - `--verbose` - use verbose output - `-v`, `--version` - prints the Phoenix installer version When passing the `--no-ecto` flag, Phoenix generators such as `phx.gen.html`, `phx.gen.json`, `phx.gen.live`, and `phx.gen.context` may no longer work as expected as they generate context files that rely on Ecto for the database access. In those cases, you can pass the `--no-context` flag to generate most of the HTML and JSON files but skip the context, allowing you to fill in the blanks as desired. Similarly, if `--no-html` is given, the files generated by `phx.gen.html` will no longer work, as important HTML components will be missing. ## [](Mix.Tasks.Phx.New.html#module-installation)Installation [`mix phx.new`](Mix.Tasks.Phx.New.html) by default prompts you to fetch and install your dependencies. You can enable this behaviour by passing the `--install` flag or disable it with the `--no-install` flag. ## [](Mix.Tasks.Phx.New.html#module-examples)Examples ``` $ mix phx.new hello_world ``` Is equivalent to: ``` $ mix phx.new hello_world --module HelloWorld ``` Or without the HTML and JS bits (useful for APIs): ``` $ mix phx.new ~/Workspace/hello_world --no-html --no-assets ``` As an umbrella: ``` $ mix phx.new hello --umbrella ``` Would generate the following directory structure and modules: ``` hello_umbrella/ Hello.Umbrella apps/ hello/ Hello hello_web/ HelloWeb ``` You can read more about umbrella projects using the official [Elixir guide](../../elixir/dependencies-and-umbrella-projects.html#umbrella-projects) [Hex Package](https://hex.pm/packages/phx_new/1.7.16) [Hex Preview](https://preview.hex.pm/preview/phx_new/1.7.16) Search HexDocs [Download ePub version](phx_new.epub "ePub version") Built using [ExDoc](https://github.com/elixir-lang/ex_doc "ExDoc") (v0.34.0) for the [Elixir programming language](https://elixir-lang.org "Elixir") === phx-docs/phoenix.html.md === === phx-docs/phoenix_html.html.md === === phx-docs/phoenix_live_dashboard.html.md === === phx-docs/phoenix_live_view.html.md === === phx-docs/phoenix_pubsub.html.md ===

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/M-Gonzalo/cosa-sai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server