diff --git a/docs/capabilities/devvit-web/devvit_web_configuration.md b/docs/capabilities/devvit-web/devvit_web_configuration.md index 57d71804..f7927180 100644 --- a/docs/capabilities/devvit-web/devvit_web_configuration.md +++ b/docs/capabilities/devvit-web/devvit_web_configuration.md @@ -1,6 +1,6 @@ # Configure your app -The devvit.json file serves as your app's configuration file. Use it to specify entry points, configure features like [event triggers](../server/triggers) and [scheduled actions](../server/scheduler.mdx), and enable app functionality such as [image uploads](../server/media-uploads.mdx). This page covers all available devvit.json configuration options. A complete devvit.json example file is provided [here](#complete-example). +The `devvit.json` file serves as your app's configuration file. Use it to specify entry points, configure features like [event triggers](../server/triggers) and [scheduled actions](../server/scheduler.mdx), and enable app functionality such as [image uploads](../server/media-uploads.mdx). This page summarizes the schema-supported configuration options; the published JSON Schema remains authoritative. A representative `devvit.json` example is provided [here](#complete-example). ## devvit.json @@ -18,12 +18,13 @@ All configuration files should include a `$schema` property which many IDEs will Your `devvit.json` must include: -- **`name`** (required): App account name and Community URL slug. Must be 3-16 characters, start with a letter, and contain only lowercase letters, numbers, and hyphens. +- **`name`** (required): App account name and Community URL slug. Must be 3-20 characters, start with a letter, and contain only lowercase letters, numbers, and hyphens. Additionally, you must include at least one of: - **`post`**: For web view apps - **`server`**: For Node.js server apps +- **`blocks`**: Deprecated migration configuration for legacy Blocks apps ## Configuration sections @@ -31,15 +32,16 @@ Additionally, you must include at least one of: | Property | Type | Description | Required | | --------- | ------ | ------------------------------------------------------------------------- | ---------------- | -| `name` | string | App account name and Community URL slug (3-16 chars, `^[a-z][a-z0-9-]*$`) | Yes | +| `name` | string | App account name and Community URL slug (3-20 chars, `^[a-z][a-z0-9-]*$`) | Yes | | `$schema` | string | Schema version for IDE support | No (recommended) | ### App components -| Property | Type | Description | Required | -| -------- | ------ | ---------------------------------- | ------------------ | -| `post` | object | Custom post/web view configuration | One of post/server | -| `server` | object | Node.js server configuration | One of post/server | +| Property | Type | Description | Required | +| -------- | ------ | ------------------------------------------------ | ------------------------- | +| `post` | object | Custom post/web view configuration | One of post/server/blocks | +| `server` | object | Node.js server configuration | One of post/server/blocks | +| `blocks` | object | Deprecated configuration for migrating Blocks apps | One of post/server/blocks | ### Permissions & capabilities @@ -58,17 +60,19 @@ Additionally, you must include at least one of: ### UI & interaction -| Property | Type | Description | Required | -| -------- | ------ | ----------------------------------------- | -------- | -| `menu` | object | Menu items in posts, comments, subreddits | No | -| `forms` | object | Form submission endpoints | No | +| Property | Type | Description | Required | +| ---------- | ------ | ------------------------------------------------ | -------- | +| `menu` | object | Menu items in posts, comments, and subreddits | No | +| `forms` | object | Form submission endpoints | No | +| `settings` | object | Global and per-subreddit app settings definitions | No | ### Development -| Property | Type | Description | Required | -| --------- | ------ | --------------------------------------------------- | -------- | -| `dev` | object | Development configuration | No | -| `scripts` | object | Build commands run by the Devvit CLI (optional) | No | +| Property | Type | Description | Required | +| --------------- | ------ | ------------------------------------------------------- | -------- | +| `dev` | object | Development configuration | No | +| `scripts` | object | Build commands run by the Devvit CLI | No | +| `sourceIgnores` | array | Source files excluded from packages submitted for review | No | ## Detailed configuration @@ -96,7 +100,7 @@ Configure web views for custom post types: - `entrypoints` (object): Map of named entrypoints for post rendering - Must include a `"default"` entrypoint - `entry` (string): HTML file path or `/api/` endpoint - - `height` (enum): `"regular"` or `"tall"` (default: `"regular"`) + - `height` (enum): `"regular"` or `"tall"` (default: `"tall"`) ### Server configuration @@ -105,14 +109,17 @@ Configure Node.js server functionality: ```json { "server": { - "entry": "src/server/index.js" + "dir": "dist/server", + "entry": "index.js" } } ``` **Properties:** -- `entry` (string): Server bundle filename (default: `"src/server/index.js"`) +- `dir` (string): Server bundle directory relative to the project root (default: `"dist/server"`) +- `entry` (string): Server bundle filename within `server.dir` (default: `"index.js"`) +- `externalEndpoints` (object): Named `/external/` routes available to approved outside callers. See [External Endpoints](../server/external-endpoints.mdx). Server bundles must be compiled to CommonJS (`cjs`). ES module output is not supported by the Devvit Web runtime. @@ -128,9 +135,11 @@ Control what your app can access: "domains": ["example.com", "api.github.com"] }, "media": true, + "journeys": true, "payments": false, "realtime": false, "redis": true, + "chromeless": false, "reddit": { "enable": true, "asUser": ["SUBMIT_POST", "SUBMIT_COMMENT"] @@ -153,9 +162,12 @@ Control what your app can access: **Other permissions:** - `media` (boolean): Enable media uploads (default: `false`) -- `payments` (boolean): Enable payments plugin (default: `false`) +- `journeys` (boolean): Enable [Devvit Journeys](../analytics/devvit-journeys.md) telemetry (default: `false`) +- `payments` (boolean): Enable the payments plugin (default: `false`) - `realtime` (boolean): Enable realtime messaging (default: `false`) - `redis` (boolean): Enable Redis storage (default: `false`) +- `chromeless` (boolean): Allow supported custom posts to render without standard post chrome when highlighted (default: `false`) +- `blob` (boolean): Reserved for the experimental Blob Storage capability and not yet publicly available. Follow the current [Blob Storage](../server/blob-storage.mdx) setup instructions rather than enabling this field unless directed by Reddit. ### Triggers configuration @@ -176,6 +188,7 @@ Handle Reddit events: - `onAppInstall`, `onAppUpgrade` - `onPostCreate`, `onPostDelete`, `onPostSubmit`, `onPostUpdate`, `onPostReport`, `onPostFlairUpdate`, `onPostNsfwUpdate`, `onPostSpoilerUpdate` - `onCommentCreate`, `onCommentDelete`, `onCommentSubmit`, `onCommentUpdate`, `onCommentReport` +- `onMentionInCommentCreate` (limited access; see [Global Triggers](../server/global-triggers.mdx)) - `onModAction`, `onModMail` - `onAutomoderatorFilterPost`, `onAutomoderatorFilterComment` @@ -264,6 +277,66 @@ Map form identifiers to submission endpoints: } ``` +### Static asset configuration + +Configure a directory of static assets that is available to both the app client and server: + +```json +{ + "media": { + "dir": "assets" + } +} +``` + +- `dir` (string): Static asset directory relative to the project root (default: `"assets"`) + +This top-level `media` object is separate from `permissions.media`, which enables runtime media uploads. + +### Settings configuration + +Define settings that apply globally or can be configured separately for each subreddit installation: + +```json +{ + "settings": { + "global": { + "featureEnabled": { + "type": "boolean", + "label": "Enable feature", + "defaultValue": true + } + }, + "subreddit": { + "welcomeMessage": { + "type": "paragraph", + "label": "Welcome message" + } + } + } +} +``` + +At least one of `global` or `subreddit` is required. Supported setting types include strings, paragraphs, numbers, booleans, selects, and multi-selects. See [Settings and Secrets](../server/settings-and-secrets.mdx) for validation and access patterns. + +### Payments configuration + +The top-level `payments` object declares products and the internal endpoints used to fulfill or refund orders. It is separate from `permissions.payments`, which enables the payments plugin. + +```json +{ + "payments": { + "productsFile": "products.json", + "endpoints": { + "fulfillOrder": "/internal/payments/fulfill-order", + "refundOrder": "/internal/payments/refund-order" + } + } +} +``` + +Provide either inline `products` or a `productsFile`, and always configure `endpoints.fulfillOrder`. See [Add Payments](../../earn-money/payments/payments_add.mdx) for the complete product schema and implementation steps. + ### Marketing assets Configure app presentation: @@ -298,6 +371,18 @@ Configure build commands run by the Devvit CLI. These commands run relative to t - `dev` (string): Command run by `devvit playtest` to build or watch your client/server artifacts - `build` (string): Command run by `devvit upload` to build your client/server artifacts +### Source packaging exclusions + +Use `sourceIgnores` to exclude files from the source archive submitted during `devvit publish`. Patterns use `.gitignore` syntax and are evaluated after the root `.gitignore` file. + +```json +{ + "sourceIgnores": ["coverage/", "*.log", "fixtures/private/"] +} +``` + +Patterns are relative to the project root. Some paths, including `node_modules/`, `.env`, and `.git/`, are always excluded. + ### Development configuration Configure development settings: @@ -316,11 +401,11 @@ Configure development settings: ## Validation rules -The `devvit.json` configuration is validated against the JSON Schema at build time. Many IDEs will also underline errors as you write. Common validation errors include: +The Devvit CLI validates `devvit.json` against the JSON Schema before playtest, upload, and publish operations. Many IDEs will also underline errors as you write. Common validation errors include: - **JSON Syntax:** Adding comments or trailing commas (unsupported by JSON) - **Required Properties:** Missing the required `name` property -- **App Components:** Missing at least one of `post` or `server` +- **App Components:** Missing at least one of `post` or `server` (or deprecated `blocks` migration configuration) - **Dependencies:** Missing `server` when `triggers` is specified - **File References:** Missing files referenced in `devvit.json` - **Permissions:** Missing required permissions for used features @@ -333,7 +418,7 @@ The `devvit.json` configuration is validated against the JSON Schema at build ti 3. **Set appropriate menu scopes.** Consider whether features should be available to all users or just moderators. 4. **Validate endpoints.** Ensure all internal endpoints start with `/internal/`. 5. **Use meaningful names.** Choose descriptive names for entrypoints, tasks, and forms. -6. **Test configurations.** Validate your config with `devvit build` before deployment. +6. **Test configurations.** Use `devvit playtest` during development; `devvit playtest`, `devvit upload`, and `devvit publish` validate the configuration before packaging. ## Environment variables @@ -356,7 +441,8 @@ The `devvit.json` configuration is validated against the JSON Schema at build ti } }, "server": { - "entry": "src/server/index.js" + "dir": "dist/server", + "entry": "index.js" }, "permissions": { "http": { diff --git a/docs/capabilities/server/launch_screen_and_entry_points/launch_screen_customization.md b/docs/capabilities/server/launch_screen_and_entry_points/launch_screen_customization.md index eaaa1616..9bdddb6f 100644 --- a/docs/capabilities/server/launch_screen_and_entry_points/launch_screen_customization.md +++ b/docs/capabilities/server/launch_screen_and_entry_points/launch_screen_customization.md @@ -28,9 +28,9 @@ import { requestExpandedMode } from '@devvit/web/client'; document.addEventListener('DOMContentLoaded', () => { const playButton = document.getElementById('play-button'); - playButton.addEventListener('click', async (event) => { + playButton.addEventListener('click', (event) => { try { - await requestExpandedMode(event, 'game'); + requestExpandedMode(event, 'game'); } catch (error) { console.error('Failed to enter expanded mode:', error); } @@ -47,13 +47,13 @@ Requests expanded mode for the web view. This displays the web view in a larger ```tsx import { requestExpandedMode } from '@devvit/web/client'; -// Must be called from a trusted event (click, touch, etc.) -await requestExpandedMode(event, 'game'); +// Must be called synchronously from a trusted click event. +requestExpandedMode(event, 'game'); ``` **Parameters** -- `event` (PointerEvent): The gesture that triggered the request, must be a trusted event +- `event` (MouseEvent): The trusted `click` event that triggered the request - `entry` (string): The destination URI name (e.g., `splash` or `game`). Entry names are the `devvit.json post.entrypoints` keys ### getWebViewMode() @@ -72,23 +72,21 @@ if (currentMode === 'expanded') { } ``` -### Mode Change Events +### Returning to inline mode -Listen for mode changes to update your UI. +The legacy `addWebViewModeListener()` and `removeWebViewModeListener()` APIs are deprecated. Listen for the window `focus` event to detect when an expanded web view returns to inline mode, then read the current mode again. ```tsx -import { addWebViewModeListener, removeWebViewModeListener } from '@devvit/web/client'; +import { getWebViewMode } from '@devvit/web/client'; function useWebViewMode() { const [mode, setMode] = useState(getWebViewMode()); useEffect(() => { - const handleModeChange = (newMode: 'inline' | 'expanded') => { - setMode(newMode); - }; + const handleFocus = () => setMode(getWebViewMode()); - addWebViewModeListener(handleModeChange); - return () => removeWebViewModeListener(handleModeChange); + window.addEventListener('focus', handleFocus); + return () => window.removeEventListener('focus', handleFocus); }, []); return mode; @@ -103,8 +101,6 @@ import { getWebViewMode, requestExpandedMode, exitExpandedMode, - addWebViewModeListener, - removeWebViewModeListener, } from '@devvit/web/client'; export function GameApp() { @@ -112,33 +108,35 @@ export function GameApp() { const [gameStarted, setGameStarted] = useState(false); useEffect(() => { - const handleModeChange = (newMode: 'inline' | 'expanded') => { - setMode(newMode); + const handleFocus = () => { + const nextMode = getWebViewMode(); + setMode(nextMode); - // Pause game when exiting expanded mode - if (newMode === 'inline' && gameStarted) { + // Pause game when returning to inline mode. + if (nextMode === 'inline' && gameStarted) { pauseGame(); } }; - addWebViewModeListener(handleModeChange); - return () => removeWebViewModeListener(handleModeChange); + window.addEventListener('focus', handleFocus); + return () => window.removeEventListener('focus', handleFocus); }, [gameStarted]); - const handlePlayClick = async (event: React.MouseEvent) => { + const handlePlayClick = (event: React.MouseEvent) => { try { - await requestExpandedMode(event.nativeEvent, 'game'); + requestExpandedMode(event.nativeEvent, 'game'); + setMode('expanded'); setGameStarted(true); } catch (error) { console.error('Could not enter expanded mode:', error); - // Fallback: start game inline + // Fallback: start game inline. setGameStarted(true); } }; - const handleExitClick = async (event: React.MouseEvent) => { + const handleExitClick = (event: React.MouseEvent) => { try { - await exitExpandedMode(event.nativeEvent); + exitExpandedMode(event.nativeEvent); } catch (error) { console.error('Could not exit expanded mode:', error); } diff --git a/docs/capabilities/server/redis.mdx b/docs/capabilities/server/redis.mdx index 2c12261c..f5c0f45b 100644 --- a/docs/capabilities/server/redis.mdx +++ b/docs/capabilities/server/redis.mdx @@ -12,14 +12,14 @@ You can add a database to your app to store and retrieve data. The Redis plugin - [Hashes](#hash) for managing a collection of key-value pairs - [Bitfields](#bitfield) for efficient operation on sequences of bits -Redis is a good model for most community games, mod tools, settings, leaderboards, and user state. If your app needs cross-community data, like a global leaderboard or aggregated analytics, design for that explicitly. Instead of assuming every installation can access the same Redis keys, use a shared service exposed through [HTTP Fetch](https://github.com/reddit/devvit-docs/blob/69f35f2254617e4a1347686235a560a78d94535b/versioned_docs/version-0.12/capabilities/server/http-fetch.mdx) to manage and serve cross-community data. +Redis is a good model for most community games, mod tools, settings, leaderboards, and user state. If your app needs cross-community data, like a global leaderboard or aggregated analytics, design for that explicitly. Instead of assuming every installation can access the same Redis keys, use a shared service exposed through [HTTP Fetch](./http-fetch.mdx) to manage and serve cross-community data. ## Data storage Each installation of an app is uniquely name-spaced, which means Redis data is siloed by subreddit. Keep in mind that there won’t be a single source of truth for all installations of your app, since each app installation can only access the data that it has stored in the Redis database. :::note -If your app stores user content from Reddit, make sure you remove it from your app when that content is deleted from Reddit. Devvit provides post and comment delete events through triggers to help with this. See [Enable and respect user deletions](https://github.com/reddit/devvit-docs/blob/69f35f2254617e4a1347686235a560a78d94535b/versioned_docs/version-0.12/devvit_rules.md#enable-and-respect-user-deletions) in the Devvit Rules. +If your app stores user content from Reddit, make sure you remove it from your app when that content is deleted from Reddit. Devvit provides post and comment delete events through triggers to help with this. See [Enable and respect user deletions](../../devvit_rules.md#enable-and-respect-user-deletions) in the Devvit Rules. ::: ## Key design @@ -60,7 +60,7 @@ Use these patterns when you need set-like or queue-like behavior. | Dense flags or compact counters | Use bitfield when you need bitmap-style storage for many small values. | | Temporary data | Use expire for short-lived keys, or remove old sorted set entries by score. | -For command-specific behavior and limits, see the [supported Redis commands](https://github.com/reddit/devvit-docs/blob/69f35f2254617e4a1347686235a560a78d94535b/versioned_docs/version-0.12/capabilities/server/redis.mdx#supported-redis-commands). +For command-specific behavior and limits, see the [supported Redis commands](#supported-redis-commands). ## Storage values @@ -75,7 +75,7 @@ await redisCompressed.set('cache:heavy_payload', JSON.stringify(payload)); The compressed client compresses supported writes when compression saves space, and decompresses those values on supported reads. -:::tip: Do not mix `redis` and `redisCompressed` on the same key. Data written by `redisCompressed` should be read with `redisCompressed`. To migrate existing data, read the old values with the standard client and write them back with the compressed client. For a complete migration example, see [Compression](https://github.com/reddit/devvit-docs/blob/69f35f2254617e4a1347686235a560a78d94535b/versioned_docs/version-0.12/capabilities/server/redis.mdx#compression-experimental). +:::tip: Do not mix `redis` and `redisCompressed` on the same key. Data written by `redisCompressed` should be read with `redisCompressed`. To migrate existing data, read the old values with the standard client and write them back with the compressed client. For a complete migration example, see [Compression](#compression-experimental). ::: ## Shared state @@ -93,18 +93,28 @@ Good candidates for transactions include: You usually do not need a transaction for single-command atomic operations, like `incrBy`, `hIncrBy`, or `zIncrBy`, unless the update depends on a separate read or validation step. -``` - async function deductBalance(userId: string, cost: number): Promise { -const key = `user:${userId}:balance`; const txn = await redis.watch(key); -const currentRaw = await txn.get(key); const current = currentRaw ? Number.parseInt(currentRaw, 10) : 0; -if (current < cost) { await txn.unwatch(); return false; } -await txn.multi(); await txn.set(key, String(current - cost)); -const result = await txn.exec(); return result !== null; } +```ts +async function deductBalance(userId: string, cost: number): Promise { + const key = `user:${userId}:balance`; + const txn = await redis.watch(key); + const currentRaw = await txn.get(key); + const current = currentRaw ? Number.parseInt(currentRaw, 10) : 0; + + if (current < cost) { + await txn.unwatch(); + return false; + } + + await txn.multi(); + await txn.set(key, String(current - cost)); + const result = await txn.exec(); + return result !== null; +} ``` Transactions are limited to 30 concurrent transaction blocks per installation, and transaction execution has a 5-second timeout. Always call unwatch() or discard() when your code exits early before exec(). -For more details, see [Transactions](https://github.com/reddit/devvit-docs/blob/69f35f2254617e4a1347686235a560a78d94535b/versioned_docs/version-0.12/capabilities/server/redis.mdx#transactions). +For more details, see [Transactions](#transactions). ## Storage limit failures @@ -157,8 +167,8 @@ This pattern works well for: The Redis reference includes a full scheduled migration example. The Scheduler reference covers recurring and one-off jobs, including platform limits for runJob(). -* [Redis migration example](https://github.com/reddit/devvit-docs/blob/69f35f2254617e4a1347686235a560a78d94535b/versioned_docs/version-0.12/capabilities/server/redis.mdx#migration-example) -* [Scheduler](https://github.com/reddit/devvit-docs/blob/69f35f2254617e4a1347686235a560a78d94535b/versioned_docs/version-0.12/capabilities/server/scheduler.mdx) +* [Redis migration example](#migration-example) +* [Scheduler](./scheduler.mdx) ## Platform limits diff --git a/docs/changelog.md b/docs/changelog.md index b4de7f9d..400aeb0e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -56,13 +56,13 @@ Additional improvements in this release include: - **Increased app slug length**. App slugs can now be up to 20 characters. -## Relese 0.13.5: Improved Tracing +## Release 0.13.5: Improved Tracing **Release Date: June 23, 2026** In this release, we've improved trace propagation between web view and plugin calls. Apps running v0.13.5+ will have more complete traces, providing better observability and simplifying debugging and support. -## Relese 0.13.4: Updated Redis Docs +## Release 0.13.4: Updated Redis Docs **Release Date: June 15, 2026** In this release, we’ve updated our [Redis](./capabilities/server/redis.mdx) documentation based on your feedback, adding clearer guidance around key design, data structures, shared states, and scheduled maintenance. diff --git a/docs/guides/tools/devvit_cli.mdx b/docs/guides/tools/devvit_cli.mdx index fe985951..77df7e74 100644 --- a/docs/guides/tools/devvit_cli.mdx +++ b/docs/guides/tools/devvit_cli.mdx @@ -170,11 +170,11 @@ $ npx devvit logs [app-name] [-d ] [-j] [-s ] [--verbo The subreddit name. The "r/" prefix is optional. -- `app-name` +#### Optional arguments - The app name (defaults to working directory app). +- `app-name` -#### Optional arguments + The app name. Defaults to the app in the working directory. - `-d , --dateformat ` @@ -214,34 +214,40 @@ $ npx devvit logs mySubreddit my-app --since 15s $ npx devvit logs mySubreddit my-app --verbose ``` -### devvit new +### devvit init -Create a new app. +Initialize a new app. `devvit new` is retained as an alias. + +Without an initialization code, this command opens the app-creation flow in your browser. Outside a Devvit project, the generated project is placed in a directory named after the new app. Inside an uninitialized Devvit project, the command initializes that directory. Use `--force` only to replace an app that has already been initialized. #### Usage ```bash -$ npx devvit new [directory-name] [--here] +$ npx devvit init [code] [--template ] [--force] ``` #### Optional arguments -- `directory-name` +- `code` + + Initialization code supplied by Reddit. If omitted, the CLI opens the browser flow to create the app and obtain a code. - Directory name for your new app project. This creates a new directory for your app code. If no name is entered, you will be prompted to choose one. +- `--template ` -- `--here` + Select a project template without opening the template-selection prompt. - Generate the project here and not in a subdirectory. +- `--force` + + Initialize a new app even when the current directory is already a Devvit app. #### Examples ```bash -$ npx devvit new +$ npx devvit init -$ npx devvit new tic-tac-toe +$ npx devvit init -$ npx devvit new --here +$ npx devvit init --template ``` ### devvit playtest @@ -251,14 +257,23 @@ Installs your app to your test subreddit and starts a playtest session. A new ve #### Usage ```bash -$ npx devvit playtest +$ npx devvit playtest [subreddit] [--debounce ] [logging options] ``` -#### Optional argument +#### Optional arguments + +- `subreddit` -- subreddit Name of a test subreddit with less than 200 subscribers that you moderate. The "r/" prefix is optional. +- `--debounce ` + + Debounce file-change handling by the specified number of milliseconds. + +- Logging options + + `playtest` accepts the same public log-formatting and filtering flags as [`devvit logs`](#devvit-logs), including `--dateformat`, `--json`, `--log-runtime`, `--show-timestamps`, `--since`, and `--verbose`. + If no subreddit is specified, the command will use the first available option from: - DEVVIT_SUBREDDIT environment variable @@ -309,9 +324,11 @@ $ npx devvit uninstall [app-name] Name of the subreddit. The "r/" prefix is optional. Requires moderator permissions in the subreddit. +#### Optional argument + - `app-name` - Name of the app (defaults to the working directory app). + Name of the app. Defaults to the app in the working directory. #### Examples @@ -333,6 +350,42 @@ Update @devvit project dependencies to the currently installed CLI's version $ npx devvit update app ``` +### devvit publish + +Create a new app version, upload the app and its source for review, and submit a publish request. + +#### Usage + +```bash +$ npx devvit publish [--bump major|minor|patch | --version ] [--public] [--withdraw] [--copy-paste] [--verbose] +``` + +#### Optional arguments + +- `--bump