Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 109 additions & 23 deletions docs/capabilities/devvit-web/devvit_web_configuration.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -18,28 +18,30 @@ 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

### Core properties

| 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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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.

Expand All @@ -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"]
Expand All @@ -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

Expand All @@ -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`

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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()
Expand All @@ -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;
Expand All @@ -103,42 +101,42 @@ import {
getWebViewMode,
requestExpandedMode,
exitExpandedMode,
addWebViewModeListener,
removeWebViewModeListener,
} from '@devvit/web/client';

export function GameApp() {
const [mode, setMode] = useState(getWebViewMode());
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<HTMLButtonElement>) => {
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<HTMLButtonElement>) => {
try {
await exitExpandedMode(event.nativeEvent);
exitExpandedMode(event.nativeEvent);
} catch (error) {
console.error('Could not exit expanded mode:', error);
}
Expand Down
Loading