Replit provides Object Storage backed by Google Cloud Storage for handling static assets like images, videos, and documents. Upload files through the Object Storage pane in your workspace, access them via auto-generated public URLs, and reference those URLs in your code. For deployed apps, remember that the local filesystem resets on every publish, so you must store persistent files in Object Storage rather than the project directory.
Store and Serve Static Files in Replit with Object Storage
This tutorial explains how to handle static assets like images, PDFs, and media files in Replit projects. You will learn why the local filesystem is not reliable for persistent storage in deployed apps, how to use Replit Object Storage for durable file hosting, and how to serve files through public URLs or a dedicated public folder during development. By the end, you will have a working pattern for uploading and serving assets in both development and production.
Prerequisites
- A Replit account on any paid plan (Core or Pro recommended for deployments)
- A working Repl with a web app (Node.js, Python, or any framework)
- Basic understanding of how web apps serve images and files
- Familiarity with environment variables and the Replit Secrets tool
Step-by-step guide
Understand filesystem non-persistence in deployments
Understand filesystem non-persistence in deployments
Before storing any files, you need to understand a critical Replit behavior: the filesystem resets every time you publish a deployment. Files you save to disk during development will not exist in production after a redeploy. This catches many beginners off guard. If your app allows users to upload profile pictures and you save them to a local uploads folder, those files vanish on the next deploy. This is by design because Replit deployments run on fresh containers. You must use external storage for any files that need to persist.
Expected result: You understand that only Object Storage and databases persist data across deployments.
Open Object Storage in your workspace
Open Object Storage in your workspace
In your Replit workspace, click the plus icon on the Tools dock in the left sidebar. Search for Object Storage and click it to open the storage pane. Object Storage is backed by Google Cloud Storage and provides a simple interface for uploading, listing, and deleting files. Each file you upload gets a publicly accessible URL that you can use in your application. The storage pane shows all uploaded files with their names, sizes, and URLs.
Expected result: The Object Storage pane opens showing an empty file list or any previously uploaded files.
Upload static assets through the UI
Upload static assets through the UI
Click the Upload button in the Object Storage pane and select files from your computer. You can upload images, fonts, PDFs, videos, and any other static file. After upload completes, each file gets a public URL that looks like a Google Cloud Storage endpoint. Click the copy icon next to any file to copy its URL. You can organize files by using path-style names like images/logo.png or assets/hero-banner.jpg. These URLs are permanent and do not change between deployments.
Expected result: Your files appear in the Object Storage list with copyable public URLs.
Reference stored files in your application code
Reference stored files in your application code
Use the public URLs from Object Storage directly in your application. In React components, set image src attributes to the storage URL. In CSS, use the URL for background images. For server-side rendering, you can fetch files from the URL using standard HTTP libraries. Store the base URL as an environment variable in Secrets if you want to switch storage providers later without changing code throughout your app.
1// React component example2import React from 'react';34const STORAGE_BASE = process.env.REPLIT_OBJECT_STORAGE_URL || '';56export function Logo() {7 return (8 <img9 src={`${STORAGE_BASE}/images/logo.png`}10 alt="Company Logo"11 width={200}12 height={60}13 />14 );15}1617export function HeroBanner() {18 return (19 <div20 style={{21 backgroundImage: `url(${STORAGE_BASE}/images/hero.jpg)`,22 backgroundSize: 'cover',23 height: '400px'24 }}25 >26 <h1>Welcome</h1>27 </div>28 );29}Expected result: Your app displays images and assets loaded from Object Storage URLs.
Use a public folder for development-only static files
Use a public folder for development-only static files
During development, you can place static files in a public or static folder at the root of your project. Most frameworks like React (via Vite), Next.js, and Express serve files from this folder automatically. This is convenient for assets that ship with your code like favicons, fonts, and placeholder images. Since these files are part of your project, they persist across deployments because they are included in the build output. However, user-uploaded content should never go here because the filesystem resets.
1// Express.js: serve a public folder2import express from 'express';3const app = express();45// Serve files from the public directory6app.use('/assets', express.static('public'));78// Now files in /public/logo.png are accessible at /assets/logo.png9app.listen(3000, '0.0.0.0', () => {10 console.log('Server running on port 3000');11});Expected result: Static files in the public folder are accessible via your app's URL during development and after deployment.
Handle file uploads programmatically with Object Storage
Handle file uploads programmatically with Object Storage
For apps that accept user uploads, write server-side code that receives the file and stores it in Object Storage using the Replit client library. The @replit/object-storage package provides simple put and get methods. Install it via the Shell with npm install @replit/object-storage. After storing a file, save the returned URL to your database so your app can reference it later. This pattern ensures uploaded files survive deployments and container resets.
1import { Client } from '@replit/object-storage';2import express from 'express';3import multer from 'multer';45const app = express();6const storage = new Client();7const upload = multer({ storage: multer.memoryStorage() });89app.post('/upload', upload.single('file'), async (req, res) => {10 try {11 const fileName = `uploads/${Date.now()}-${req.file.originalname}`;12 await storage.uploadFromBytes(fileName, req.file.buffer);13 const url = await storage.getSignedUrl(fileName);14 res.json({ success: true, url });15 } catch (error) {16 console.error('Upload failed:', error);17 res.status(500).json({ error: 'Upload failed' });18 }19});2021app.listen(3000, '0.0.0.0');Expected result: Uploaded files are stored in Object Storage and the API returns a URL you can use in your frontend.
Complete working example
1import express from 'express';2import multer from 'multer';3import { Client } from '@replit/object-storage';4import path from 'path';56const app = express();7const storage = new Client();8const upload = multer({9 storage: multer.memoryStorage(),10 limits: { fileSize: 5 * 1024 * 1024 } // 5 MB limit11});1213// Serve static assets from the public folder14app.use('/assets', express.static('public'));1516// Serve the upload form17app.get('/', (req, res) => {18 res.send(`19 <h1>File Upload Demo</h1>20 <form action="/upload" method="POST" enctype="multipart/form-data">21 <input type="file" name="file" accept="image/*,.pdf" />22 <button type="submit">Upload</button>23 </form>24 <div id="files"></div>25 `);26});2728// Handle file upload to Object Storage29app.post('/upload', upload.single('file'), async (req, res) => {30 if (!req.file) {31 return res.status(400).json({ error: 'No file provided' });32 }3334 try {35 const ext = path.extname(req.file.originalname);36 const fileName = `uploads/${Date.now()}${ext}`;3738 await storage.uploadFromBytes(fileName, req.file.buffer);39 const url = await storage.getSignedUrl(fileName);4041 res.json({42 success: true,43 fileName,44 url,45 size: req.file.size46 });47 } catch (error) {48 console.error('Upload error:', error.message);49 res.status(500).json({ error: 'Failed to store file' });50 }51});5253// List all stored files54app.get('/files', async (req, res) => {55 try {56 const files = await storage.list();57 res.json({ files });58 } catch (error) {59 console.error('List error:', error.message);60 res.status(500).json({ error: 'Failed to list files' });61 }62});6364// Delete a stored file65app.delete('/files/:name', async (req, res) => {66 try {67 await storage.delete(req.params.name);68 res.json({ success: true });69 } catch (error) {70 console.error('Delete error:', error.message);71 res.status(500).json({ error: 'Failed to delete file' });72 }73});7475const PORT = 3000;76app.listen(PORT, '0.0.0.0', () => {77 console.log(`Server running on port ${PORT}`);78});Common mistakes when storing static files in Replit
Why it's a problem: Saving uploaded files to a local folder like /uploads and expecting them to persist in production
How to avoid: Use Replit Object Storage instead. The filesystem resets on every deployment, so local files are lost.
Why it's a problem: Binding the server to localhost or 127.0.0.1 instead of 0.0.0.0
How to avoid: Always use 0.0.0.0 as the host. Replit deployments cannot detect ports bound to localhost, causing the error hostingpid1: an open port was not detected.
Why it's a problem: Hardcoding Object Storage URLs directly in frontend code instead of using environment variables
How to avoid: Store the base storage URL in Secrets (Tools > Secrets) and reference it via process.env. This makes it easy to change providers later.
Why it's a problem: Uploading very large files without setting size limits, causing the Repl to run out of memory
How to avoid: Configure multer or your upload handler with a fileSize limit. On the free tier (2 GiB RAM), keep uploads under 5 MB per file.
Best practices
- Never store user-uploaded files on the local filesystem in Replit because deployments reset the disk on every publish
- Use Object Storage for any file that needs to persist across deployments, including images, documents, and media
- Store the Object Storage base URL in Secrets so you can swap providers without changing application code
- Set file size limits on upload endpoints to prevent out-of-memory crashes, especially on the free tier with 2 GiB RAM
- Use path-style naming conventions like uploads/2024/image.png to keep Object Storage organized
- Bind your server to 0.0.0.0 instead of localhost so Replit deployments can detect the open port
- Place build-time static assets like favicons and fonts in the public folder since they deploy with your code
- Monitor storage usage in the Resources panel to stay within your plan allocation
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a web app on Replit that needs to handle user-uploaded images. The filesystem resets on deployment. How do I use Replit Object Storage to persist uploaded files and serve them via public URLs? Give me a complete Express.js example with multer for uploads and the @replit/object-storage client library.
Add a file upload feature to my app. Users should be able to upload images up to 5 MB. Store files in Replit Object Storage so they persist across deployments. Show uploaded images in a gallery page. Use Express with multer on the backend and display a simple upload form on the frontend.
Frequently asked questions
No. The filesystem resets every time you publish a deployment. Files written to disk during runtime are lost when the container restarts. Use Object Storage for any files that need to persist.
Object Storage is a file hosting service backed by Google Cloud Storage. It provides durable storage with public URLs for your files. Storage costs depend on your plan and usage. Check the Resources panel in your workspace for current allocation and usage details.
Yes, for assets that ship with your code like favicons, fonts, and icons. These files are included in the build output and deploy with your app. However, do not use the public folder for user-uploaded content because it requires a redeploy to update.
Copy the public URL from the Object Storage pane and use it as the src attribute in an img tag. For dynamic content, store the base URL in Secrets and build full paths in code using process.env or import.meta.env for Vite-based projects.
Yes. You can place a CDN in front of your Object Storage URLs by configuring the CDN to proxy requests to the storage endpoint. This adds caching and reduces latency for global users.
Any file type is supported, including images (PNG, JPG, SVG, WebP), documents (PDF, DOCX), media (MP4, MP3), fonts (WOFF2, TTF), and data files (JSON, CSV). There is no restriction on file format.
You are likely saving files to the local filesystem instead of Object Storage. Replit deployments run on fresh containers that do not retain files from previous runs. Switch to Object Storage to persist uploads permanently.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation