strapi/strapi · error

Invalid GitHub template URL: ${template}

Error message

Invalid GitHub template URL: ${template}

What it means

Thrown by copyTemplate() when a GitHub URL template's pathname has a 3rd segment that is neither undefined nor 'tree'. Strapi parses GitHub URLs as /owner/repo/tree/branch/path; any other 3rd segment (e.g. 'blob', 'commit') is rejected as an unsupported template URL shape.

Source

Thrown at packages/cli/create-strapi-app/src/utils/template.ts:74

        retries: 3,
        onRetry(err, attempt) {
          console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
        },
      }
    );

    return;
  }

  if (isGithubRepo(template)) {
    const url = new URL(template);

    const [owner, repo, t, branch, ...pathSegments] = stripTrailingSlash(
      url.pathname.slice(1)
    ).split('/');

    if (t !== undefined && t !== 'tree') {
      throw new Error(`Invalid GitHub template URL: ${template}`);
    }

    if (scope.templateBranch) {
      await retry(
        () =>
          downloadGithubRepo(rootPath, {
            owner,
            repo,
            branch: scope.templateBranch,
            subPath: scope.templatePath,
          }),
        {
          retries: 3,
          onRetry(err, attempt) {
            console.log(`Retrying to download the template. Attempt ${attempt}. Error: ${err}`);
          },
        }
      );

View on GitHub (pinned to 4a4101264d)

Solutions

  1. Use a URL copied from the repo's branch/tree view, or omit the path and use `owner/repo` shorthand.
  2. Replace `/blob/` with `/tree/` in the URL.
  3. Use the shorthand `owner/repo[/subpath]` form instead of a full URL.
  4. Pin the branch via `--template-branch` and pass a simpler template URL.

Example fix

// before
npx create-strapi-app my-app --template https://github.com/foo/bar/blob/main/template
// after
npx create-strapi-app my-app --template https://github.com/foo/bar/tree/main/template
Defensive patterns

Strategy: validation

Validate before calling

// Validate GitHub URL shape before calling copyTemplate
function isValidTemplateUrl(u: string): boolean {
  try {
    const url = new URL(u);
    if (url.origin !== 'https://github.com') return false;
    const [, , , t] = url.pathname.split('/');
    return t === undefined || t === 'tree';
  } catch { return false; }
}

Type guard

const isTreeGithubUrl = (u: string): boolean => {
  try {
    const url = new URL(u);
    if (url.origin !== 'https://github.com') return false;
    const seg = url.pathname.split('/').filter(Boolean);
    return seg.length >= 2 && (seg.length === 2 || seg[2] === 'tree');
  } catch { return false; }
};

Try / catch

try {
  await copyTemplate(scope, rootPath);
} catch (e) {
  if (/Invalid GitHub template URL/.test(e.message)) {
    console.error('Use a /tree/ GitHub URL or the owner/repo shorthand.');
  } else throw e;
}

Prevention

When it happens

Trigger: scope.template passes isGithubRepo() (https://github.com origin) so copyTemplate runs new URL(template) and splits the pathname into [owner, repo, t, branch, ...pathSegments]; throws when t is defined and !== 'tree' (e.g. the user pasted a /blob/ URL).

Common situations: User pasted a `https://github.com/.../blob/main/...` URL copied from viewing a file instead of the repo tree; URL points to a commit, pull, or wiki path; the URL was constructed by a tool using a non-tree GitHub route.

Related errors


AI-assisted analysis of strapi/strapi@4a4101264d (2026-08-12). Data as JSON: /api/errors/21c847576a349b4d. Report an issue: GitHub.