gitroomhq/postiz-app · error · HttpException

All media must be uploaded through our upload API route and

Error message

All media must be uploaded through our upload API route and contain the domain: ${process.env.RESTRICT_UPLOAD_DOMAINS}

What it means

createPost rejects any image whose path does not contain the RESTRICT_UPLOAD_DOMAINS env value. When media domain restriction is enabled, all media referenced in posts must have been uploaded through the platform's own upload API route, so third-party/hotlinked media URLs are refused with a 400.

Source

Thrown at apps/backend/src/public-api/routes/v1/public.integrations.controller.ts:215

    Sentry.metrics.count('public_api-request', 1);
    const body = await this._postsService.mapTypeToPost(
      rawBody,
      org.id,
      rawBody?.type === 'draft' || true
    );
    body.type = rawBody.type;

    if (
      process.env.RESTRICT_UPLOAD_DOMAINS &&
      body.posts.some((p) =>
        p.value.some((a) =>
          a.image.some(
            (i) => i.path.indexOf(process.env.RESTRICT_UPLOAD_DOMAINS) === -1
          )
        )
      )
    ) {
      throw new HttpException(
        {
          msg: `All media must be uploaded through our upload API route and contain the domain: ${process.env.RESTRICT_UPLOAD_DOMAINS}`,
        },
        400
      );
    }

    // Server-side validation — same rules as the dashboard, surfaced as a
    // readable 400 (see PostValidationExceptionFilter).
    const validation = await this._postsService.validatePosts(
      org.id,
      body.posts
    );

    const fail = (item: (typeof validation)[number], error: string) => {
      throw new PostValidationException({
        provider: item.identifier,
        name: item.name,

View on GitHub (pinned to 0f1647f749)

Solutions

  1. First upload the image via the public upload endpoint and use the returned URL in the post
  2. If self-hosting, set RESTRICT_UPLOAD_DOMAINS to your own upload/CDN domain so platform-uploaded media passes the check
  3. If you intentionally want external media allowed, unset/empty RESTRICT_UPLOAD_DOMAINS (only if your deployment's security model permits)
  4. Ensure the configured domain matches exactly the domain in returned upload URLs (no scheme/port mismatch in the substring check)

Example fix

// before
{ "image": [{ "path": "https://images.example.com/cat.jpg" }] }

// after
const upload = await postiz.upload.fromUrl({ url: 'https://images.example.com/cat.jpg' });
{ "image": [{ "path": upload.url }] } // served under RESTRICT_UPLOAD_DOMAINS
Defensive patterns

Strategy: validation

Validate before calling

const domain = process.env.RESTRICT_UPLOAD_DOMAINS;
const paths = post.posts.flatMap(p => p.value.flatMap(v => v.image ?? []).map(i => i.path));
if (domain && paths.some(p => !p.includes(domain))) {
  // upload these through the API first, then swap in returned URLs
}

Type guard

const isAllowedMediaPath = (path: string): boolean =>
  !process.env.RESTRICT_UPLOAD_DOMAINS || path.includes(process.env.RESTRICT_UPLOAD_DOMAINS);

Try / catch

try {
  await api.createPost(body);
} catch (e) {
  if (String(e?.response?.data?.msg).includes('RESTRICT_UPLOAD_DOMAINS')) {
    // re-upload media via the upload route and retry with returned URLs
  }
}

Prevention

When it happens

Trigger: POST /public/v1/posts with an image entry whose path is an external URL (e.g. https://images.unsplash.com/...) while the RESTRICT_UPLOAD_DOMAINS env var is set. The check is a substring indexOf on each image path, so any media not originating from the configured upload domain fails.

Common situations: RESTRICT_UPLOAD_DOMAINS set in the environment (often by default or copied from production .env) while the client hotlinks external images; custom self-hosted deployments forgetting to point the env at their own upload/CDN domain; clients migrating from an API that allowed arbitrary URLs.

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/771fd64fcb3e67f0. Report an issue: GitHub.