### Start Playtest Session with Devvit CLI
Source: https://developers.reddit.com/docs/guides/tools/devvit_cli
Installs your app to a test subreddit and starts a continuous playtest session. Changes are automatically reflected upon saving, and logs are streamed in real-time. Press Ctrl+C to end the session.
```bash
npx devvit playtest
```
--------------------------------
### Complete Example
Source: https://developers.reddit.com/docs/capabilities/server/launch_screen_and_entry_points/launch_screen_customization
A complete example demonstrating the usage of launch screen customization and mode management.
```APIDOC
## Complete Example
### Description
This example demonstrates how to integrate launch screen customization and manage view modes within a React application.
### Code
```typescript
import React, { useState, useEffect } from 'react';
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);
// Pause game when exiting expanded mode
if (newMode === 'inline' && gameStarted) {
pauseGame();
}
};
addWebViewModeListener(handleModeChange);
return () => removeWebViewModeListener(handleModeChange);
}, [gameStarted]);
const handlePlayClick = async (event: React.MouseEvent) => {
try {
await requestExpandedMode(event.nativeEvent, 'game');
setGameStarted(true);
} catch (error) {
console.error('Could not enter expanded mode:', error);
// Fallback: start game inline
setGameStarted(true);
}
};
const handleExitClick = async (event: React.MouseEvent) => {
try {
await exitExpandedMode(event.nativeEvent);
} catch (error) {
console.error('Could not exit expanded mode:', error);
}
};
if (mode === 'inline') {
return (
Adventure Game
Tap to play in fullscreen
);
}
return (
);
}
```
```
--------------------------------
### Install @devvit/start
Source: https://developers.reddit.com/docs/guides/tools/vite
Run this command to install the necessary Vite plugin for Devvit projects.
```bash
npm install @devvit/start
```
--------------------------------
### Example of using get() with TxClientLike
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/TxClientLike
Illustrates retrieving the value of a Redis key using the `get` method. Returns `null` if the key does not exist.
```typescript
async function getExample(context: Devvit.Context) {
await context.redis.set("quantity", "5");
const quantity : string | undefined = await context.redis.get("quantity");
console.log("Quantity: " + quantity);
}
```
--------------------------------
### Install Latest @devvit/web
Source: https://developers.reddit.com/docs/guides/migrate/devvit-web-experimental
Install the latest version of the @devvit/web package to begin the migration.
```bash
npm install @devvit/web@latest
```
--------------------------------
### Complete Game App Example
Source: https://developers.reddit.com/docs/capabilities/server/launch_screen_and_entry_points/launch_screen_customization
A comprehensive React example demonstrating how to manage inline and expanded views, handle mode changes, and integrate game logic.
```typescript
import React, { useState, useEffect } from 'react';
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);
// Pause game when exiting expanded mode
if (newMode === 'inline' && gameStarted) {
pauseGame();
}
};
addWebViewModeListener(handleModeChange);
return () => removeWebViewModeListener(handleModeChange);
}, [gameStarted]);
const handlePlayClick = async (event: React.MouseEvent) => {
try {
await requestExpandedMode(event.nativeEvent, 'game');
setGameStarted(true);
} catch (error) {
console.error('Could not enter expanded mode:', error);
// Fallback: start game inline
setGameStarted(true);
}
};
const handleExitClick = async (event: React.MouseEvent) => {
try {
await exitExpandedMode(event.nativeEvent);
} catch (error) {
console.error('Could not exit expanded mode:', error);
}
};
if (mode === 'inline') {
return (
Adventure Game
Tap to play in fullscreen
);
}
return (
);
}
```
--------------------------------
### Install Latest @devvit/web Package
Source: https://developers.reddit.com/docs/llms-full.txt
Command to update the `@devvit/web` package to the latest version, essential for migrating to the final Devvit Web setup.
```bash
npm install @devvit/web@latest
```
--------------------------------
### Redis String Operations Example
Source: https://developers.reddit.com/docs/capabilities/server/redis
Demonstrates setting a string, getting a range, overwriting part of a string, and checking its length using Redis commands.
```javascript
async function stringsExample() {
// First, set 'word' to 'tacocat'
await redis.set('word', 'tacocat');
// Use getRange() to get the letters in 'word' between index 0 to 3, inclusive
console.log('Range from index 0 to 3: ' + (await redis.getRange('word', 0, 3)));
// Use setRange() to insert 'blue' at index 0
await redis.setRange('word', 0, 'blue');
console.log('Word after using setRange(): ' + (await redis.get('word')));
// Use strLen() to verify the word length
console.log('Word length: ' + (await redis.strLen('word')));
}
```
```text
Range from index 0 to 3: taco
Word after using setRange(): bluecat
Word length: 7
```
--------------------------------
### Install App to Subreddit with Devvit CLI
Source: https://developers.reddit.com/docs/guides/tools/devvit_cli
Installs a specified app from the Apps directory to a subreddit you moderate. You can install the latest version or a specific version.
```bash
npx devvit install [app-name]@[version]
```
```bash
npx devvit install r/mySubreddit
```
```bash
npx devvit install mySubreddit my-app
```
```bash
npx devvit install r/mySubreddit my-app@1.2.3
```
```bash
npx devvit install r/mySubreddit @1.2.3
```
--------------------------------
### Devvit JSON Configuration Example
Source: https://developers.reddit.com/docs/llms-full.txt
Example `devvit.json` configuration specifying entry points for client and server builds. The Vite plugin uses this file as the source of truth for client entry points.
```json
{
"post": {
"dir": "dist/client",
"entrypoints": {
"default": {
"inline": true,
"entry": "splash.html"
}
}
},
"server": {
"dir": "dist/server",
"entry": "index.ts"
}
}
```
--------------------------------
### Install App to Subreddit
Source: https://developers.reddit.com/docs/llms-full.txt
Installs the latest non-playtest version of your app to a specified subreddit. You can also specify a particular version to install.
```bash
devvit install
```
```bash
devvit install [@version]
```
--------------------------------
### products.json Schema Example
Source: https://developers.reddit.com/docs/earn-money/payments/payments_add
This example demonstrates the structure of the `products.json` file, including product attributes like SKU, display name, description, price, image, metadata, and accounting type.
```json
{
"$schema": "https://developers.reddit.com/schema/products.json",
"products": [
{
"sku": "god_mode",
"displayName": "God mode",
"description": "God mode gives you superpowers (in theory)",
"price": 25,
"images": {
"icon": "products/extra_life_icon.png"
},
"metadata": {
"category": "powerup"
},
"accountingType": "CONSUMABLE"
}
]
}
```
--------------------------------
### Redis String Operations Example
Source: https://developers.reddit.com/docs/llms-full.txt
Demonstrates setting a string, getting a substring, overwriting part of a string, and checking its length using Redis commands.
```typescript
async function stringsExample() {
// First, set 'word' to 'tacocat'
await redis.set('word', 'tacocat');
// Use getRange() to get the letters in 'word' between index 0 to 3, inclusive
console.log('Range from index 0 to 3: ' + (await redis.getRange('word', 0, 3)));
// Use setRange() to insert 'blue' at index 0
await redis.setRange('word', 0, 'blue');
console.log('Word after using setRange(): ' + (await redis.get('word')));
// Use strLen() to verify the word length
console.log('Word length: ' + (await redis.strLen('word')));
}
```
```bash
Range from index 0 to 3: taco
Word after using setRange(): bluecat
Word length: 7
```
--------------------------------
### Install Express for Server Endpoints
Source: https://developers.reddit.com/docs/guides/migrate/inline-web-view
Install the Express.js framework to set up server endpoints in your Devvit application. This is a prerequisite for creating a custom server.
```bash
npm i express
```
--------------------------------
### Server-Side HTTP Fetch Example
Source: https://developers.reddit.com/docs/capabilities/http-fetch
Make GET requests to allow-listed external domains from your server-side code. Ensure the domain is configured in `devvit.json` and the request does not exceed the 30-second timeout.
```typescript
const response = await fetch('https://example.com/api/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const data = await response.json();
console.log('External API response:', data);
```
--------------------------------
### List Installed Apps on Subreddit with Devvit CLI
Source: https://developers.reddit.com/docs/guides/tools/devvit_cli
Lists all apps currently installed on a specified subreddit. If no subreddit is provided, it lists all apps installed by the current user.
```bash
npx devvit list installs [subreddit]
```
```bash
npx devvit list installs
```
```bash
npx devvit list installs mySubreddit
```
```bash
npx devvit list installs r/mySubreddit
```
--------------------------------
### Complete Interactive Post Example with Devvit
Source: https://developers.reddit.com/docs/llms-full.txt
This example demonstrates creating an interactive post with a splash screen, post data for game state, and includes logic for updating post data and submitting a comment as a user. It utilizes `reddit.submitCustomPost`, `reddit.getPostById`, and `updatedPost.setPostData`.
```typescript
import { reddit, context } from '@devvit/web/server';
// Create an interactive post with all features
const post = await reddit.submitCustomPost({
subredditName: context.subredditName!,
title: 'Interactive Game Post',
// First Screen Customization (splash screen)
splash: {
appDisplayName: 'Number Guessing Game',
backgroundUri: 'game-bg.png',
buttonLabel: 'Start Playing',
description: 'Can you guess the secret number?',
heading: 'Welcome to the Challenge!'
},
// Post Data for game state
postData: {
secretNumber: Math.floor(Math.random() * 100) + 1,
totalGuesses: 0,
gameState: 'active',
winner: null
}
});
// Later, update post data when someone wins
const updatedPost = await reddit.getPostById(post.id); await updatedPost.setPostData({ ...context.postData, gameState: 'completed', winner: currentUsername, totalGuesses: context.postData.totalGuesses + 1 });
// Create a comment as the user announcing their win await reddit.submitComment({ runAs: 'USER', id: post.id, text: 🎉 ${currentUsername} won the game! });
```
--------------------------------
### Migrate Example Data Job
Source: https://developers.reddit.com/docs/llms-full.txt
Handles the migration of example data, including compression and cursor-based iteration. It includes logic to stop early if execution time approaches the limit and requeues itself if more data needs processing.
```javascript
// The proxy detects the write and compresses the string if beneficial.
if (value && value.length > 0) {
await redisCompressed.hSet(MY_DATA_HASH_KEY, { [field]: value });
}
})
);
processedInJob += fieldValues.length;
// Cursor logic: 0 means iteration is complete
if (nextCursor === 0) {
cursor = 0;
keepRunning = false;
} else {
cursor = nextCursor;
}
// Safety: Check execution time.
// If we are close to 30s (Devvit limit), stop early and requeue.
if (Date.now() - startTime > 20000) {
console.log('[Migration] Time limit approaching, stopping early.');
keepRunning = false;
}
}
const newTotal = processedTotal + processedInJob;
// Daisy Chaining:
// If the cursor is not 0, we still have more data to scan.
// We schedule *this same job* to run again immediately.
if (cursor !== 0) {
console.log(`[Migration] Requeueing. Next cursor: ${cursor}. Processed so far: ${newTotal}`);
await scheduler.runJob({
name: 'migrate-example-data',
runAt: new Date(),
data: {
cursor,
chunkSize,
processed: newTotal,
},
});
res.json({ status: 'requeued', processed: newTotal, cursor });
} else {
console.log(`[Migration] COMPLETE. Total items processed: ${newTotal}`);
res.json({ status: 'success', processed: newTotal });
}
} catch (error) {
console.error('[Migration] Critical Job Error', error);
res.status(500).json({ status: 'error', message: error.message });
}
},
);
```
--------------------------------
### Install Devvit Payments Package
Source: https://developers.reddit.com/docs/llms-full.txt
Add the Devvit payments functionality to an existing app by installing the `@devvit/payments` npm package.
```bash
npm install @devvit/payments
```
--------------------------------
### Get Range Example
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/RedisClient
Retrieves a substring from a Redis key based on start and end offsets. Ensure the key exists and the offsets are valid.
```typescript
async function getRangeExample(context: Devvit.Context) {
await context.redis.set("word", "tacocat");
const range : string = await context.redis.getRange("word", 0, 3)
console.log("Range from index 0 to 3: " + range);
}
```
--------------------------------
### Client-Side HTTP Fetch Example
Source: https://developers.reddit.com/docs/capabilities/http-fetch
Make GET requests to your own webview's API endpoints from client-side code. Requests must target endpoints starting with `/api` and cannot access external domains.
```typescript
const handleFetchData = async () => {
// Correct: fetching your own webview's API endpoint
const response = await fetch("/api/user-data", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
console.log("API response:", data);
};
// Incorrect: cannot fetch external domains from client-side
// const response = await fetch('https://external-api.com/data');
// Incorrect: endpoint must start with /api
// const response = await fetch('/user-data');
```
--------------------------------
### Complete Devvit.json Configuration Example
Source: https://developers.reddit.com/docs/capabilities/devvit-web/devvit_web_configuration
This is a comprehensive example of a devvit.json file. It demonstrates how to configure application name, post settings, server entrypoints, permissions (HTTP and Redis), triggers, menu items, scheduler tasks, marketing assets, development subreddit, and build scripts.
```json
{
"$schema": "https://developers.reddit.com/schema/config-file.v1.json",
"name": "my-awesome-app",
"post": {
"dir": "public",
"entrypoints": {
"default": {
"entry": "index.html",
"height": "tall"
}
}
},
"server": {
"entry": "src/server/index.js"
},
"permissions": {
"http": {
"enable": true,
"domains": ["api.example.com"]
},
"redis": true
},
"triggers": {
"onPostCreate": "/internal/triggers/post-create"
},
"menu": {
"items": [
{
"label": "Approve",
"forUserType": "moderator",
"location": "post",
"endpoint": "/internal/menu/approve"
}
]
},
"scheduler": {
"tasks": {
"daily-cleanup": {
"endpoint": "/internal/cron/cleanup",
"cron": "0 2 * * *"
}
}
},
"marketingAssets": {
"icon": "assets/icon.png"
},
"dev": {
"subreddit": "my-test-sub"
},
"scripts": {
"dev": "vite build --watch",
"build": "vite build"
}
}
```
--------------------------------
### Create a basic app with a log action
Source: https://developers.reddit.com/docs/llms-full.txt
Configure a Devvit app with a menu item that triggers a server-side log when clicked. Ensure the `devvit.json` includes necessary permissions and server configuration.
```json
{
"$schema": "https://developers.reddit.com/schema/config-file.v1.json",
"name": "app-name",
"server": {
"dir": "dist/server",
"entry": "index.cjs"
},
"permissions": {
"reddit": true
},
"menu": {
"items": [
{
"label": "Create a log!",
"location": "subreddit",
"endpoint": "/internal/log-action",
"forUserType": "moderator"
}
]
}
}
```
--------------------------------
### Get User ID
Source: https://developers.reddit.com/docs/api/redditapi/models/classes/User
Retrieves the unique identifier for a user, which starts with 't2_'.
```javascript
't2_1w72'
```
--------------------------------
### Fetch Products and Initiate Purchase (Client)
Source: https://developers.reddit.com/docs/earn-money/payments/payments_add
Fetch product metadata from your server and use it to display products. Call `purchase(sku)` to initiate the payment flow. Requires `@devvit/web/client`.
```typescript
import { purchase, OrderResultStatus } from "@devvit/web/client";
// Fetch products from your server endpoint
const products = await fetch("/api/products").then((r) => r.json());
// Render your UI; when user chooses a product:
async function handleBuy(sku: string) {
const result = await purchase(sku);
if (result.status === OrderResultStatus.STATUS_SUCCESS) {
// show success
} else {
// show error or retry (result.errorMessage may be set)
}
}
```
--------------------------------
### Get Mod Queue Items (Posts and Comments)
Source: https://developers.reddit.com/docs/api/redditapi/RedditAPIClient/classes/RedditAPIClient
Fetches items from the moderator queue that require review. This example shows how to get both posts and comments by default, and then specifically posts.
```javascript
const subreddit = await reddit.getSubredditByName("mysubreddit")
let listing = await subreddit.getModQueue();
console.log("Posts and Comments: ", await listing.all())
listing = await subreddit.getModQueue({ type: "post"});
console.log("Posts: ", await listing.all())
```
--------------------------------
### Get Devvit CLI Version
Source: https://developers.reddit.com/docs/guides/tools/devvit_cli
Display the version of the locally installed Devvit CLI.
```bash
$ npx devvit version
```
--------------------------------
### Create Basic App Log
Source: https://developers.reddit.com/docs/guides/tools/logs
This example demonstrates how to create a basic app configuration and a server-side endpoint that logs a message when triggered by a menu action. Use `console.log`, `console.info`, or `console.error` in your server code to generate logs.
```json
{
"$schema": "https://developers.reddit.com/schema/config-file.v1.json",
"name": "app-name",
"server": {
"dir": "dist/server",
"entry": "index.cjs"
},
"permissions": {
"reddit": true
},
"menu": {
"items": [
{
"label": "Create a log!",
"location": "subreddit",
"endpoint": "/internal/log-action",
"forUserType": "moderator"
}
]
}
}
```
--------------------------------
### Get Redis Key Value as String
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/RedisClient
Example of retrieving the string value associated with a Redis key using the get command. Returns undefined if the key does not exist. Raises an exception for non-UTF-8 values.
```typescript
async function getExample(context: Devvit.Context) {
await context.redis.set("quantity", "5");
const quantity : string | undefined = await context.redis.get("quantity");
console.log("Quantity: " + quantity);
}
```
--------------------------------
### Get Vault by Address
Source: https://developers.reddit.com/docs/api/redditapi/RedditAPIClient/classes/RedditAPIClient
Fetches a Vault associated with a given blockchain address. The address must start with '0x'.
```javascript
const vault = await reddit.getVaultByAddress('0x205ee28744456bDBf180A0Fa7De51e0F116d54Ed');
```
--------------------------------
### Preview HTML for Launch Screen Customization
Source: https://developers.reddit.com/docs/llms-full.txt
An example HTML file for a launch screen. This serves as the initial inline view for the user before they engage with the app.
```html
My Game
Adventure Game
Tap to play in fullscreen
```
--------------------------------
### Get Vault by User ID
Source: https://developers.reddit.com/docs/api/redditapi/RedditAPIClient/classes/RedditAPIClient
Retrieves a Vault belonging to a specific user ID. The user ID should start with 't2_'.
```javascript
const vault = await reddit.getVaultByUserId('t2_1w72');
```
--------------------------------
### Create Custom Post with Styles
Source: https://developers.reddit.com/docs/capabilities/creating_custom_post
This example shows how to create a custom post and apply specific styles, such as background colors, height, and a share image URL.
```APIDOC
## submitCustomPost with Styles
### Description
Creates a custom post with specified styling options to control its appearance in the Reddit UI.
### Method
`reddit.submitCustomPost(options)`
### Parameters
(See `submitCustomPost` for general parameters. The following are specific to `styles`)
#### Request Body - styles
- **backgroundColor** (string) - Optional - The default background color shown before the iframe content loads. Must be in `#RRGGBBAA` format. Defaults to transparent (`#00000000`).
- **backgroundColorDark** (string) - Optional - The dark mode background color shown before the iframe content loads. Must be in `#RRGGBBAA` format. Defaults to transparent (`#00000000`).
- **height** (string) - Optional - Post height. Can be `"REGULAR"` (320px) or `"TALL"` (512px). Defaults to `"TALL"`.
- **shareImageUrl** (string) - Optional - The preview image URL used when the post is shared externally. Must be hosted on i.redd.it.
### Request Example
```javascript
await reddit.submitCustomPost({
"title": "Post with styles title",
"styles": {
"backgroundColor": "#FFFFFFFF", // white, fully opaque
"backgroundColorDark": "#000000FF", // black, fully opaque
"height": "TALL",
"shareImageUrl": "https://reddi.it/12345.png"
}
})
```
### Response
#### Success Response (200)
Returns the created post object with applied styles.
#### Response Example
(Response structure not explicitly defined in source, but would be a post object)
```
--------------------------------
### Get Subreddit Info by ID - RedditAPIClient
Source: https://developers.reddit.com/docs/api/redditapi/RedditAPIClient/classes/RedditAPIClient
Fetches a SubredditInfo object using its unique ID. Ensure the ID starts with 't5_'.
```javascript
const memes = await reddit.getSubredditInfoById('t5_2qjpg');
```
--------------------------------
### Get Comment by ID RedditAPIClient
Source: https://developers.reddit.com/docs/api/redditapi/RedditAPIClient/classes/RedditAPIClient
Retrieves a specific Comment object using its unique ID. The ID must start with 't1_'.
```javascript
const comment = await reddit.getCommentById('t1_1qjpg');
```
--------------------------------
### New Launch Screen Entry Points
Source: https://developers.reddit.com/docs/capabilities/server/launch_screen_and_entry_points/splash_migration
Demonstrates how to use `submitCustomPost()` with explicit entry points for the new launch screen system.
```typescript
await reddit.submitCustomPost({ title: 'Tennis Match #37' }); // implicitly default entry
await reddit.submitCustomPost({ title: 'Tennis Leaderboard', entry: 'leaderboard' }); // not a URI, an entry name.
```
--------------------------------
### Vite Build Configuration for Entry Points
Source: https://developers.reddit.com/docs/capabilities/server/launch_screen_and_entry_points/splash_migration
Example of configuring Vite's build process to include multiple HTML entry points.
```javascript
{
...all your normal vite nonsense...
build: {
input: ['splash.html', 'game.html'], <-- add your entrypoints
...
}
}
```
--------------------------------
### Example README Fetch Domains Section
Source: https://developers.reddit.com/docs/capabilities/http-fetch
Include a 'Fetch Domains' section in your README to list requested domains and their justifications. This is required for the approval process.
```markdown
## Fetch Domains
The following domains are requested for this app:
- `api.wikipedia.org` - Used to fetch article summaries for the knowledge base feature
- `username.supabase.com` - Required for relational database storage of user preferences (Devvit KV store doesn't support complex queries needed for this feature)
```
--------------------------------
### Client Vite Configuration Example
Source: https://developers.reddit.com/docs/guides/migrate/devvit-web-experimental
Configure Vite for client-side builds, specifying the output directory to `dist/client`.
```javascript
export default defineConfig({
build: {
outDir: '../../dist/client', // No longer webroot
},
});
```
--------------------------------
### Example README Fetch Domains Section
Source: https://developers.reddit.com/docs/capabilities/server/http-fetch-policy
Include a 'Fetch Domains' section in your app's README to list requested domains and their justifications for the approval process.
```markdown
## Fetch Domains
The following domains are requested for this app:
- `api.wikipedia.org` - Used to fetch article summaries for the knowledge base feature
- `username.supabase.com` - Required for relational database storage of user preferences (Devvit KV store doesn't support complex queries needed for this feature)
```
--------------------------------
### Redis Bitfield Operations
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/RedisClient
Demonstrates the use of the bitfield command for complex bitwise operations on Redis strings. Includes examples for incrementing and getting bits.
```typescript
async function bitfieldExample(context: Devvit.Context) {
const fooResults : number[] =
await context.redis.bitfield('foo', 'incrBy', 'i5', 100, 1, 'get', 'u4', 0);
console.log("fooResults: " + fooResults); // [1, 0]
const barResults : number[] =
await context.redis.bitfield('bar',
'set', 'u2', 0, 3,
'get', 'u2', 0,
'incrBy', 'u2', 0, 1,
'overflow', 'sat',
'get', 'u2', 0,
'set', 'u2', 0, 3,
'incrBy', 'u2', 0, 1);
console.log("barResults: " + barResults); // [0, 3, 0, 0, 3, 3]
}
```
--------------------------------
### Configure Devvit.json Entrypoints
Source: https://developers.reddit.com/docs/llms-full.txt
Define entrypoints for client, blocks, and server applications in `devvit.json`. The client and server entrypoints should point to bundled output files, while the Devvit CLI handles bundling for blocks automatically.
```json
{
"post": {
"client": {
"dir": "dist/client",
"entry": "dist/client/index.html"
}
},
"blocks": {
"entry": "src/devvit/main.tsx"
},
"server": {
"entry": "dist/server/index.cjs"
},
}
```
--------------------------------
### Get Unread Modmail Count
Source: https://developers.reddit.com/docs/api/redditapi/models/classes/ModMailService
Fetches the count of unread modmail conversations, categorized by conversation state. The example logs the 'highlighted' and 'new' counts.
```javascript
const response = await reddit.modMail.getUnreadCount();
console.log(response.highlighted);
console.log(response.new);
```
--------------------------------
### Server Vite Configuration Example
Source: https://developers.reddit.com/docs/guides/migrate/devvit-web-experimental
Configure Vite for server-side builds, ensuring correct output directories and formats as specified in `devvit.json`.
```javascript
export default defineConfig({
ssr: {
noExternal: true,
},
build: {
emptyOutDir: false,
ssr: 'index.ts',
outDir: '../../dist/server',
target: 'node22',
sourcemap: true,
rollupOptions: {
external: [...builtinModules],
output: {
format: 'cjs',
entryFileNames: 'index.cjs',
inlineDynamicImports: true,
},
},
},
});
```
--------------------------------
### Get Moderated Subreddits
Source: https://developers.reddit.com/docs/api/redditapi/models/classes/ModMailService
Retrieves a list of subreddits that the current user moderates and has mail permissions for. The example shows how to log the ID and name of each subreddit.
```javascript
const subredditsData = await reddit.modMail.getSubreddits();
for (const subreddit of Object.values(subreddits)) {
console.log(subreddit.id);
console.log(subreddit.name);
}
```
--------------------------------
### Example of using getRange() with TxClientLike
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/TxClientLike
Demonstrates extracting a substring from a Redis string value using the `getRange` method. Both start and end offsets are inclusive.
```typescript
async function getRangeExample(context: Devvit.Context) {
await context.redis.set("word", "tacocat");
const range : string = await context.redis.getRange("word", 0, 3)
console.log("Range from index 0 to 3: " + range);
}
```
--------------------------------
### Redis Menu Endpoint Definition
Source: https://developers.reddit.com/docs/capabilities/server/redis
Defines the menu endpoint that returns the form definition for initiating the migration. This setup is crucial for user interaction to start the migration process.
```typescript
import { redis, scheduler, type TaskRequest, type TaskResponse } from '@devvit/web/server';
// Import the compressed client
import { redisCompressed } from '@devvit/redis';
import type { MenuItemRequest, UiResponse } from '@devvit/web/shared';
type MigrateExampleFormRequest = {
startCursor?: string;
chunkSize?: number;
};
type MigrateExampleJobData = {
cursor?: number | string;
chunkSize?: number;
processed?: number;
};
const MY_DATA_HASH_KEY = 'my:app:large:dataset';
// 1. Menu Endpoint: Returns the form definition
app.post(
'/internal/menu/ops/migrate-example',
async (_req, res) => {
res.json({
showForm: {
name: 'migrateExampleForm', // Must match key in devvit.json "forms"
form: {
title: 'Migrate Hash to Compression',
acceptLabel: 'Start Migration',
fields: [
{
name: 'startCursor',
label: 'Start Cursor (0 for beginning)',
type: 'string',
defaultValue: '0',
},
{
name: 'chunkSize',
label: 'Items per batch',
type: 'number',
defaultValue: 20000,
},
],
},
},
});
},
);
```
--------------------------------
### Display Devvit CLI Help
Source: https://developers.reddit.com/docs/guides/tools/devvit_cli
Use this command to view general help information for the Devvit CLI.
```bash
npx devvit help
```
--------------------------------
### Add Support App Product using Devvit CLI
Source: https://developers.reddit.com/docs/earn-money/payments/support_this_app
Use the Devvit CLI to generate the necessary product configuration for the 'support-app'. This is the first step in setting up the in-app support feature.
```bash
devvit products add support-app
```
--------------------------------
### mGet Example
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/RedisClient
Use mGet to retrieve multiple values from Redis by specifying an array of keys. This is more efficient than calling GET multiple times. Note that deprecated mget() should not be used.
```typescript
async function mGetExample(context: Devvit.Context) {
await context.redis.mSet({"name": "Zeek", "occupation": "Developer"});
const result : (string | null)[] = await context.redis.mGet(["name", "occupation"]);
result.forEach(x => {
console.log(x);
});
}
```
--------------------------------
### Publish an app for public listing
Source: https://developers.reddit.com/docs/guides/launch/launch-guide
Run this command to publish your app and request it to be listed in the App Directory, making it available for any community to install. This requires a detailed README.md.
```bash
npx devvit publish --public
```
--------------------------------
### Get User by ID - RedditAPIClient
Source: https://developers.reddit.com/docs/api/redditapi/RedditAPIClient/classes/RedditAPIClient
Retrieves a User object using their unique ID. Ensure the ID starts with 't2_'. The promise may resolve to undefined if the user is not found.
```javascript
const user = await reddit.getUserById('t2_1qjpg');
```
--------------------------------
### Get User Modmail Conversations
Source: https://developers.reddit.com/docs/api/redditapi/models/classes/ModMailService
Retrieves recent posts, comments, and modmail conversations for a specific user. Requires the user's conversation ID. The example logs recent comments and posts.
```javascript
const data = await reddit.modMail.getUserConversations('abcdef');
console.log(data.recentComments);
console.log(data.recentPosts);
```
--------------------------------
### Complete Game App Example with View Mode Handling
Source: https://developers.reddit.com/docs/llms-full.txt
This example demonstrates a full React app that handles inline and expanded view modes, including requesting expanded mode, exiting it, and pausing the game when exiting expanded mode. It also includes fallback logic if expanded mode cannot be entered.
```tsx
import React, { useState, useEffect } from 'react';
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);
// Pause game when exiting expanded mode
if (newMode === 'inline' && gameStarted) {
pauseGame();
}
};
addWebViewModeListener(handleModeChange);
return () => removeWebViewModeListener(handleModeChange);
}, [gameStarted]);
const handlePlayClick = async (event: React.MouseEvent) => {
try {
await requestExpandedMode(event.nativeEvent, 'game');
setGameStarted(true);
} catch (error) {
console.error('Could not enter expanded mode:', error);
// Fallback: start game inline
setGameStarted(true);
}
};
const handleExitClick = async (event: React.MouseEvent) => {
try {
await exitExpandedMode(event.nativeEvent);
} catch (error) {
console.error('Could not exit expanded mode:', error);
}
};
if (mode === 'inline') {
return (
Adventure Game
Tap to play in fullscreen
);
}
return (
);
}
```
--------------------------------
### Get Sorted Set Member Score with zScore
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/RedisClient
Use zScore to retrieve the score associated with a specific member in a Redis Sorted Set. This example adds members to a 'leaderboard' sorted set and then fetches the score for 'caesar'.
```typescript
async function zScoreExample(context: Devvit.Context) {
await context.redis.zAdd("leaderboard",
{member: "louis", score: 37},
{member: "fernando", score: 10},
{member: "caesar", score: 20},
{member: "alexander", score: 25},
);
const score : number = await context.redis.zScore("leaderboard", "caesar");
console.log("Caesar's score: " + score);
}
```
--------------------------------
### Create an Interactive Post with Features
Source: https://developers.reddit.com/docs/capabilities/interactive-posts/interactive_posts_overview
This example demonstrates creating an interactive post with first screen customization (splash screen), post data for game state, and includes logic for updating post data and submitting a comment as the user.
```typescript
import { reddit, context } from '@devvit/web/server';
// Create an interactive post with all features
const post = await reddit.submitCustomPost({
subredditName: context.subredditName!,
title: 'Interactive Game Post',
// First Screen Customization (splash screen)
splash: {
appDisplayName: 'Number Guessing Game',
backgroundUri: 'game-bg.png',
buttonLabel: 'Start Playing',
description: 'Can you guess the secret number?',
heading: 'Welcome to the Challenge!'
},
// Post Data for game state
postData: {
secretNumber: Math.floor(Math.random() * 100) + 1,
totalGuesses: 0,
gameState: 'active',
winner: null
}
});
// Later, update post data when someone wins
const updatedPost = await reddit.getPostById(post.id); await updatedPost.setPostData({ ...context.postData, gameState: 'completed', winner: currentUsername, totalGuesses: context.postData.totalGuesses + 1 });
// Create a comment as the user announcing their win await reddit.submitComment({ runAs: 'USER', id: post.id, text: 🎉 ${currentUsername} won the game! });
```
--------------------------------
### Execute Redis Transaction with EXEC
Source: https://developers.reddit.com/docs/llms-full.txt
Use `WATCH` to monitor keys, `MULTI` to start a transaction, queue commands like `INCRBY` and `SET`, and `EXEC` to commit them. This example demonstrates updating karma and setting a name within a transaction.
```typescript
async function transactionsExample1() {
await redis.mSet({ quantity: '5', karma: '32' });
const txn = await redis.watch('quantity');
await txn.multi(); // Begin a transaction
await txn.incrBy('karma', 10);
await txn.set('name', 'Devvit');
await txn.exec(); // Execute the commands in the transaction
console.log(
'Keys after completing transaction: ' +
(await redis.mGet(['quantity', 'karma', 'name']))
);
}
```
```bash
Keys after completing transaction: 5,42,Devvit
```
--------------------------------
### README Fetch Domains Section Example
Source: https://developers.reddit.com/docs/llms-full.txt
When using fetch domains, include a 'Fetch Domains' section in your app's README. List each requested domain and explain its necessity for the approval process.
```markdown
## Fetch Domains
The following domains are requested for this app:
- `api.wikipedia.org` - Used to fetch article summaries for the knowledge base feature
- `username.supabase.com` - Required for relational database storage of user preferences (Devvit KV store doesn't support complex queries needed for this feature)
```
--------------------------------
### AppInstallDefinition
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/AppInstallDefinition
Defines the structure for app installation events, including the event itself and a handler for when the event occurs.
```APIDOC
## Type Alias: AppInstallDefinition
> **AppInstallDefinition** = `object`
## Properties
### event
> **event** : `AppInstall`
> The app installation event.
### onEvent
> **onEvent** : `TriggerOnEventHandler`<`AppInstall`>
> Handler for when an app installation event occurs.
```
--------------------------------
### Install Devvit CLI as Dev Dependency
Source: https://developers.reddit.com/docs/guides/tools/devvit_cli
Install or update the Devvit CLI as a development dependency within your project. This is the recommended installation method.
```bash
npm install --save-dev devvit@latest
```
--------------------------------
### Add Products via CLI
Source: https://developers.reddit.com/docs/llms-full.txt
Use this command to add products defined in your local src/products.json file to your app. This command is also run when you upload or playtest your app.
```bash
devvit products add
```
--------------------------------
### Uninstall Global CLI and Install as Dev Dependency
Source: https://developers.reddit.com/docs/llms-full.txt
If the CLI was installed globally, uninstall it and then install it as a development dependency within your project. This ensures version consistency.
```bash
npm uninstall -g devvit
npm install --save-dev devvit@latest
```
--------------------------------
### Install or Update Devvit App on Subreddit
Source: https://developers.reddit.com/docs/guides/faq
Install your Devvit app on a subreddit or update an existing installation. This command ensures the subreddit has the desired app version.
```bash
devvit install
```
--------------------------------
### Example devvit.json Configuration
Source: https://developers.reddit.com/docs/guides/tools/vite
The Devvit Vite plugin uses `devvit.json` to determine client entry points. Ensure at least one entrypoint is defined.
```json
{
"post": {
"dir": "dist/client",
"entrypoints": {
"default": {
"inline": true,
"entry": "splash.html"
}
}
},
"server": {
"dir": "dist/server",
"entry": "index.ts"
}
}
```
--------------------------------
### Update Global Devvit CLI
Source: https://developers.reddit.com/docs/guides/tools/devvit_cli
Update the globally installed Devvit CLI to the latest version. Note: Installing as a dev dependency is recommended over global installation.
```bash
npm install -g devvit@latest
```
--------------------------------
### Basic Server Usage of media.upload()
Source: https://developers.reddit.com/docs/capabilities/server/media-uploads
A basic example demonstrating how to upload an image using `media.upload()` on the server.
```APIDOC
## Basic server usage
```typescript
import { media } from '@devvit/web/server';
const uploaded = await media.upload({
url: 'https://example.com/my-image.png',
type: 'image',
});
// uploaded.mediaId
// uploaded.mediaUrl
```
```
--------------------------------
### mount() Method
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/UseWebViewResult
Initiates a request to open the web view.
```APIDOC
### mount()
> **mount**(): `void`
Initiate a request for the web view to open
#### Returns
`void`
```
--------------------------------
### Update Global CLI Installation
Source: https://developers.reddit.com/docs/llms-full.txt
Update a globally installed Devvit CLI to the latest version. Note that using the CLI as a dev dependency is generally recommended over global installation.
```bash
npm install -g devvit@latest
```
--------------------------------
### Create Post with Default Entry Point
Source: https://developers.reddit.com/docs/capabilities/server/launch_screen_and_entry_points/view_modes_entry_points
Use `reddit.submitCustomPost` to create a post, automatically using the 'default' entry point if none is specified.
```typescript
import { reddit } from '@devvit/web/server';
// Create a post using the default entrypoint
async function createDefaultPost(context: any) {
return await reddit.submitCustomPost({
subredditName: context.subredditName!,
title: 'Adventure Game',
entry: 'default',
postData: {
gameState: 'menu',
},
});
}
```
--------------------------------
### getBestPosts
Source: https://developers.reddit.com/docs/api/redditapi/RedditAPIClient/classes/RedditAPIClient
Gets a list of best posts from the front page. This method will get the front page for the app account by default. To get the front page for a user, please contact Reddit.
```APIDOC
## getBestPosts()
### Description
Get a list of best posts from the front page. This method will get the front page for the app account by default. To get the front page for a user, please contact Reddit.
### Parameters
#### options
- **options** (ListingFetchOptions) - Required - Options for the request
### Returns
- **Listing** - A Listing of Post objects.
### Example
```javascript
const posts = await reddit.getBestPosts({
limit: 1000,
pageSize: 100
}).all();
```
```
--------------------------------
### get()
Source: https://developers.reddit.com/docs/api/public-api/type-aliases/TxClientLike
Gets the value of a key from Redis. Returns nil if the key does not exist.
```APIDOC
## get()
### Description
Get the value of key. If the key does not exist the special value nil is returned.
### Method
`get(key: string): Promise`
### Parameters
#### key
- `key` (string) - The key to retrieve the value from.
### Returns
- `Promise` - A promise that resolves to the value of the key or null if the key does not exist.
### Example
```javascript
async function getExample(context) {
await context.redis.set("quantity", "5");
const quantity = await context.redis.get("quantity");
console.log("Quantity: " + quantity);
}
```
```
--------------------------------
### Configure Multiple Entry Points in devvit.json
Source: https://developers.reddit.com/docs/capabilities/server/launch_screen_and_entry_points/view_modes_entry_points
Define different entry points for your application in `devvit.json`. Each entry point can specify a unique HTML file and configuration, allowing users to launch your app from various contexts.
```json
{
"post": {
"dir": "dist/client",
"entrypoints": {
"default": {
"entry": "src/client/preview.html",
"height": "regular",
"inline": true
},
"game": {
"entry": "src/client/game.html"
},
"leaderboard": {
"entry": "src/client/leaderboard.html"
}
}
}
}
```