Skip to main content

Media storage

Some social platforms can upload local files directly. Others require every image or video to be reachable at a public URL before the post can be created. Instagram is the most common example.

SimplePost supports temporary public storage through S3-compatible providers such as AWS S3, DigitalOcean Spaces, and Cloudflare R2. Cloudflare R2 is a good default because the free tier is generous for most small deployments.

When you need it

You need public media storage when:

  • The Scheduler app accepts browser uploads.
  • A platform requires media URLs instead of direct file upload.
  • The MCP server or API receives uploaded media that platforms must fetch.
  • You want reliable publishing from environments where local files are not reachable by the platform.

You may not need it when:

  • You use the SDK or CLI with platforms that accept direct file upload.
  • You provide already-public media URLs.
  • You use the self-hosted REST server's local /media/:filename route and the server is publicly reachable.

Environment variables

S3_STORAGE_ACCESS_KEY_ID=
S3_STORAGE_SECRET_ACCESS_KEY=
S3_STORAGE_REGION=
S3_STORAGE_BUCKET=
S3_STORAGE_BASE_URL=
S3_STORAGE_ENDPOINT=
VariableMeaning
S3_STORAGE_ACCESS_KEY_IDAccess key for your storage provider.
S3_STORAGE_SECRET_ACCESS_KEYSecret key for your storage provider.
S3_STORAGE_REGIONStorage region. Use auto for Cloudflare R2.
S3_STORAGE_BUCKETBucket name.
S3_STORAGE_BASE_URLPublic base URL where uploaded files are reachable.
S3_STORAGE_ENDPOINTS3-compatible endpoint URL.

Cloudflare R2 outline

  1. Create an R2 bucket.
  2. Create an R2 API token with object read/write permissions for the bucket.
  3. Configure a public custom domain or public bucket URL for reads.
  4. Set the S3_STORAGE_* variables in Scheduler, the REST server, or the SDK runtime.
  5. Add a bucket CORS rule when a browser will PUT to presigned URLs.
  6. Upload a small image and verify that the returned URL is publicly reachable without cookies or auth headers.

For the hosted Scheduler, the minimum R2 browser-upload rule is:

[
{
"AllowedOrigins": ["https://app.simplepost.social"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]

Use your own exact application origins for self-hosted deployments. Presigned URLs authorize anyone holding the URL to PUT to one object until expiry, so keep them out of logs and analytics.

Platforms fetch media server-side, so a URL that only works in your browser session is not enough.

Self-hosted REST server media

The self-hosted REST server accepts streaming multipart uploads on local disk and serves them from:

/media/:filename

Set SIMPLE_POST_PUBLIC_URL to the public server origin so returned media URLs are usable by social platforms:

SIMPLE_POST_PUBLIC_URL=https://posts.example.com

If the server is behind a firewall or only reachable on localhost, uploaded media will not work for platforms that need public URLs.

For direct object-storage uploads, configure the same S3_STORAGE_* variables and call POST /api/v1/upload/presign. The response contains a short-lived PUT URL and a media object ready for validation or posting. The bucket CORS origin must be the browser application making the PUT, not the REST server origin unless they are the same.