> ## Documentation Index
> Fetch the complete documentation index at: https://backstage.spotify.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting started Guide

> Set up your Portal instance by configuring your domain, connecting an identity provider like GitHub, Okta, or Entra ID, and completing initial onboarding.

export const InteractiveSetup = () => {
  const [currentStep, setCurrentStep] = useState(1);
  const [selections, setSelections] = useState({
    auth: null,
    repo: null,
    orgData: null
  });
  const githubPermissions = [{
    permission: 'Administration',
    access: 'Read / Write',
    reason: 'Enables Portal to manage repository settings, webhooks, and collaborators'
  }, {
    permission: 'Actions',
    access: 'Read / Write',
    reason: 'Allows Portal to view and trigger GitHub Actions workflows'
  }, {
    permission: 'Code scanning alerts',
    access: 'Read',
    reason: 'Provides access to security vulnerabilities detected in code'
  }, {
    permission: 'Commit statuses',
    access: 'Read',
    reason: 'Enables Portal to display build and test statuses on commits'
  }, {
    permission: 'Contents',
    access: 'Read / Write',
    reason: 'Allows Portal to read repository files and create/update scaffolded code'
  }, {
    permission: 'Dependabot alerts',
    access: 'Read',
    reason: 'Provides access to dependency vulnerability alerts'
  }, {
    permission: 'Issues',
    access: 'Read / Write',
    reason: 'Enables Portal to create, view, and manage repository issues'
  }, {
    permission: 'Metadata',
    access: 'Read',
    reason: 'Required for basic repository information access'
  }, {
    permission: 'Pull requests',
    access: 'Read / Write',
    reason: 'Allows Portal to create and manage pull requests for code changes'
  }, {
    permission: 'Repository security advisories',
    access: 'Read',
    reason: 'Provides access to security advisory information'
  }, {
    permission: 'Secret scanning alerts',
    access: 'Read',
    reason: 'Allows Portal to access detected leaked secrets in repositories'
  }];
  const githubOrgPermissions = [{
    permission: 'Members',
    access: 'Read',
    reason: 'Allows Portal to access organization member information for user management and authentication'
  }];
  const entraDelegatedPermissions = ['email', 'offline_access', 'openid', 'profile', 'User.Read'];
  const entraApplicationPermissions = ['GroupMember.Read.All', 'User.Read.All'];
  const [expandedPermissions, setExpandedPermissions] = useState({});
  const togglePermissions = sectionKey => {
    setExpandedPermissions(prev => ({
      ...prev,
      [sectionKey]: !prev[sectionKey]
    }));
  };
  const buttonStyle = {
    padding: '20px 32px',
    border: '2px solid #1DB954',
    borderRadius: '8px',
    cursor: 'pointer',
    transition: 'all 0.2s ease',
    fontSize: '16px',
    fontWeight: '600',
    minWidth: '140px',
    textAlign: 'center'
  };
  const primaryButtonStyle = {
    ...buttonStyle,
    padding: '24px 40px',
    fontSize: '18px',
    fontWeight: '700',
    minWidth: '160px',
    boxShadow: '0 2px 8px rgba(29, 185, 84, 0.2)'
  };
  const handleAuthSelection = auth => {
    setSelections(prev => ({
      ...prev,
      auth
    }));
    setCurrentStep(2);
  };
  const handleRepoSelection = repo => {
    setSelections(prev => ({
      ...prev,
      repo
    }));
    setCurrentStep(3);
  };
  const handleOrgDataSelection = orgData => {
    setSelections(prev => ({
      ...prev,
      orgData
    }));
    setCurrentStep(4);
  };
  const getConfiguration = () => {
    const {auth, repo, orgData} = selections;
    if (auth === 'github' && repo === 'github' && orgData === 'github') {
      return {
        title: 'GitHub Only (Minimal)',
        description: 'Simple setup using GitHub for everything',
        requirements: [{
          component: 'GitHub OAuth App',
          admin: 'GitHub Admin',
          credentials: 'clientId, clientSecret'
        }, {
          component: 'GitHub App Setup',
          admin: 'GitHub Org Admin',
          credentials: 'appId, clientId, clientSecret, privateKey'
        }],
        guide: 'guides/auth/github'
      };
    }
    if (auth === 'okta' && repo === 'github') {
      return {
        title: 'Okta + GitHub',
        description: 'Okta for user authentication, GitHub for code and data',
        requirements: [{
          component: 'Okta OAuth App',
          admin: 'Okta Admin',
          credentials: 'clientId, clientSecret, domain'
        }, {
          component: 'GitHub App Setup',
          admin: 'GitHub Org Admin',
          credentials: 'appId, clientId, clientSecret, privateKey'
        }],
        guides: ['guides/auth/okta', 'guides/auth/github']
      };
    }
    if (auth === 'entra' && repo === 'github') {
      return {
        title: 'Entra ID + GitHub',
        description: 'Microsoft Entra ID for authentication, GitHub for code',
        requirements: [{
          component: 'Entra ID OAuth App',
          admin: 'Azure Admin',
          credentials: 'clientId, clientSecret, tenantId'
        }, {
          component: 'GitHub App Setup',
          admin: 'GitHub Org Admin',
          credentials: 'appId, clientId, clientSecret, privateKey'
        }],
        guides: ['guides/auth/entraId', 'guides/auth/github']
      };
    }
    if (auth === 'okta' && repo === 'azure-devops') {
      return {
        title: 'Okta + Azure DevOps',
        description: 'Okta for authentication, Azure DevOps for code and data',
        requirements: [{
          component: 'Okta OAuth App',
          admin: 'Okta Admin',
          credentials: 'clientId, clientSecret, domain'
        }, {
          component: 'Azure DevOps App Setup',
          admin: 'Azure DevOps Admin',
          credentials: 'clientId, clientSecret, tenantId, endpoints'
        }],
        guides: ['guides/auth/okta']
      };
    }
    if (auth === 'entra' && repo === 'azure-devops') {
      return {
        title: 'Entra ID + Azure DevOps',
        description: 'Microsoft Entra ID for authentication and Azure DevOps integration',
        requirements: [{
          component: 'Entra ID OAuth App',
          admin: 'Azure Admin',
          credentials: 'clientId, clientSecret, tenantId'
        }, {
          component: 'Azure DevOps App Setup',
          admin: 'Azure DevOps Admin',
          credentials: 'clientId, clientSecret, tenantId, endpoints'
        }],
        guides: ['guides/auth/entraId']
      };
    }
    return {
      title: `${auth} + ${repo}`,
      description: 'Custom configuration',
      requirements: [{
        component: `${auth} OAuth App`,
        admin: `${auth} Admin`,
        credentials: 'clientId, clientSecret'
      }, {
        component: `${repo} App Setup`,
        admin: `${repo} Admin`,
        credentials: 'Various credentials needed'
      }]
    };
  };
  const getImplementationSteps = () => {
    const {auth, repo, orgData} = selections;
    const steps = [];
    if (auth === 'github') {
      steps.push({
        title: 'GitHub Authentication Setup',
        steps: ['Create GitHub OAuth App in your organization settings', 'Configure Homepage URL and Authorization callback URL:', 'Save clientId and clientSecret (these will be added to your Portal configuration later)'],
        callbackUrl: 'https://<YOUR_COMPANY>.spotifyportal.com/api/auth/github/handler/frame'
      });
    } else if (auth === 'okta') {
      steps.push({
        title: 'Okta Authentication Setup',
        steps: ['Create new application in Okta Admin Console', 'Configure as Web Application with Authorization Code flow', 'Set redirect URIs for Portal callback:', 'Save clientId, clientSecret, and domain (these will be added to your Portal configuration later)', 'Assign users/groups to the application'],
        callbackUrl: 'https://<YOUR_COMPANY>.spotifyportal.com/api/auth/okta/handler/frame'
      });
    } else if (auth === 'entra') {
      steps.push({
        title: 'Entra ID Authentication Setup',
        steps: ['Register new application in Azure App Registrations', 'Configure Web platform with redirect URIs:', 'Generate client secret in Certificates & secrets', 'Save clientId, clientSecret, and tenantId (these will be added to your Portal configuration later)', 'Grant required Microsoft Graph permissions (see details below)'],
        callbackUrl: 'https://<YOUR_COMPANY>.spotifyportal.com/api/auth/microsoft/handler/frame',
        hasPermissions: true,
        permissionsKey: 'entra-auth'
      });
    }
    if (repo === 'github') {
      steps.push({
        title: 'GitHub Repository Integration',
        steps: ['Create GitHub App in your organization settings', 'Configure repository and organization permissions (see details below)', 'Disable webhooks (not required for Portal)', 'Generate and download private key', 'Install app on your organization and required repositories', 'Save appId, clientId, clientSecret, and privateKey (these will be added to your Portal configuration later)'],
        hasPermissions: true,
        permissionsKey: 'github-repo'
      });
    } else if (repo === 'azure-devops') {
      steps.push({
        title: 'Azure DevOps Integration',
        steps: ['Register application in Azure DevOps organization settings', 'Configure required scopes (Code, Project and team, Identity)', 'Set callback URL for OAuth flow', 'Save clientId, clientSecret, tenantId, and endpoints (these will be added to your Portal configuration later)']
      });
    } else if (repo === 'gitlab') {
      steps.push({
        title: 'GitLab Integration',
        steps: ['Create OAuth application in GitLab instance/group settings', 'Configure redirect URI and required scopes', 'Save clientId, clientSecret, and baseUrl (these will be added to your Portal configuration later)']
      });
    }
    return steps;
  };
  const resetWizard = () => {
    setCurrentStep(1);
    setSelections({
      auth: null,
      repo: null,
      orgData: null
    });
    setTimeout(() => {
      const wizardElement = document.querySelector('[data-wizard="interactive-setup"]');
      if (wizardElement) {
        wizardElement.scrollIntoView({
          behavior: 'smooth',
          block: 'start'
        });
      }
    }, 100);
  };
  const config = currentStep === 4 ? getConfiguration() : null;
  return <div data-wizard="interactive-setup" className="rounded-2xl border border-gray-950/10 dark:border-white/10" style={{
    padding: '24px'
  }}>
      <div className="flex justify-between items-center mb-5">
        <h3 style={{
    margin: 0
  }}>Setup Configuration Wizard</h3>
        {currentStep <= 3 && <div style={{
    fontSize: '14px',
    padding: '4px 12px'
  }}>
            Step {currentStep} of 3
          </div>}
      </div>

      {currentStep === 1 && <div>
          <h4>What do you use for your identity provider?</h4>
          <p style={{
    fontSize: '14px'
  }}>
            This is how your team members will sign into Portal
          </p>

          <div className="flex gap-4 flex-wrap justify-center mt-5">
            <button onClick={() => handleAuthSelection('github')} style={primaryButtonStyle}>
              GitHub
            </button>
            <button onClick={() => handleAuthSelection('okta')} style={primaryButtonStyle}>
              Okta
            </button>
            <button onClick={() => handleAuthSelection('entra')} style={primaryButtonStyle}>
              Entra ID (Azure AD)
            </button>
          </div>
        </div>}

      {currentStep === 2 && <div>
          <div className="mb-4 text-sm">
            ✅ Identity Provider: <strong>{selections.auth}</strong>
          </div>
          <h4>Where are your code repositories?</h4>
          <p style={{
    fontSize: '14px'
  }}>
            Where your team stores source code and documentation
          </p>
          <div className="flex gap-3 flex-wrap mt-5">
            <button onClick={() => handleRepoSelection('github')} style={buttonStyle}>
              GitHub
            </button>
            <button onClick={() => handleRepoSelection('azure-devops')} style={buttonStyle}>
              Azure DevOps
            </button>
            <button onClick={() => handleRepoSelection('gitlab')} style={buttonStyle}>
              GitLab
            </button>
          </div>
          <div className="mt-4">
            <button onClick={() => setCurrentStep(1)} style={{
    padding: '8px 16px',
    cursor: 'pointer',
    fontSize: '14px'
  }}>
              ← Back
            </button>
          </div>
        </div>}

      {currentStep === 3 && <div>
          <div className="mb-4 text-sm">
            ✅ Identity Provider: <strong>{selections.auth}</strong>
            <br />✅ Repositories: <strong>{selections.repo}</strong>
          </div>
          <h4>Where is your team and user information?</h4>
          <p style={{
    fontSize: '14px'
  }}>
            Where Portal should get team structures, user profiles, and
            organizational data
          </p>
          <div className="flex gap-3 flex-wrap mt-5">
            <button onClick={() => handleOrgDataSelection(selections.auth)} style={buttonStyle}>
              Same as identity provider ({selections.auth})
            </button>
            <button onClick={() => handleOrgDataSelection(selections.repo)} style={buttonStyle}>
              Same as repositories ({selections.repo})
            </button>
          </div>
          <div style={{
    marginTop: '16px'
  }}>
            <button onClick={() => setCurrentStep(2)} style={{
    padding: '8px 16px',
    cursor: 'pointer',
    fontSize: '14px'
  }}>
              ← Back
            </button>
          </div>
        </div>}

      {currentStep === 4 && config && <div>
          <Check>
            <h4 style={{
    marginBottom: '8px'
  }}>
              Your Configuration: {config.title}
            </h4>
            {config.description}
          </Check>

          <h4>What you'll need to set up:</h4>

          <table style={{
    marginBottom: '20px'
  }}>
            <thead style={{
    color: 'rgb(var(--gray-700))'
  }}>
              <tr>
                <th>Component</th>
                <th>Who Sets It Up</th>
                <th>Required Credentials</th>
              </tr>
            </thead>
            <tbody>
              {config.requirements.map((req, index) => <tr key={index}>
                  <td>{req.component}</td>
                  <td>{req.admin}</td>
                  <td>{req.credentials}</td>
                </tr>)}
            </tbody>
          </table>

          <h4>Next Steps:</h4>
          <div style={{
    margin: '20px 0'
  }}>
            {getImplementationSteps().map((section, index) => <div key={index} style={{
    marginBottom: '20px'
  }}>
                <h5>
                  {index + 1}. {section.title}
                </h5>
                <div className="ml-4">
                  <ol style={{
    margin: 0,
    paddingLeft: '20px',
    fontSize: '14px'
  }}>
                    {section.steps.map((step, stepIndex) => <li key={stepIndex} style={{
    marginBottom: '4px'
  }}>
                        {step}
                        {section.callbackUrl && (step.includes('callback') || step.includes('redirect')) && <pre>{section.callbackUrl}</pre>}
                      </li>)}
                  </ol>

                  {}
                  {section.hasPermissions && section.permissionsKey === 'github-repo' && <div style={{
    marginTop: '12px'
  }}>
                        <button onClick={() => togglePermissions(section.permissionsKey)} style={{
    background: 'none',
    padding: '6px 12px',
    cursor: 'pointer',
    fontSize: '13px',
    display: 'flex',
    alignItems: 'center',
    gap: '6px'
  }}>
                          {expandedPermissions[section.permissionsKey] ? '▼' : '▶'}{' '}
                          View Required Permissions
                        </button>

                        {expandedPermissions[section.permissionsKey] && <div style={{
    marginTop: '8px',
    padding: '12px',
    fontSize: '13px'
  }}>
                            <div style={{
    marginBottom: '12px'
  }}>
                              <strong>Repository Permissions:</strong>
                              <div style={{
    marginTop: '6px'
  }}>
                                {githubPermissions.map((perm, permIndex) => <div key={permIndex} style={{
    marginBottom: '4px',
    display: 'flex',
    gap: '8px'
  }}>
                                    <span style={{
    minWidth: '140px',
    fontWeight: '500'
  }}>
                                      {perm.permission}:
                                    </span>
                                    <span style={{
    minWidth: '80px'
  }}>
                                      {perm.access}
                                    </span>
                                    <span>- {perm.reason}</span>
                                  </div>)}
                              </div>
                            </div>

                            <div>
                              <strong>Organization Permissions:</strong>
                              <div style={{
    marginTop: '6px'
  }}>
                                {githubOrgPermissions.map((perm, permIndex) => <div key={permIndex} style={{
    marginBottom: '4px',
    display: 'flex',
    gap: '8px'
  }}>
                                    <span style={{
    minWidth: '140px',
    fontWeight: '500'
  }}>
                                      {perm.permission}:
                                    </span>
                                    <span style={{
    minWidth: '80px'
  }}>
                                      {perm.access}
                                    </span>
                                    <span>- {perm.reason}</span>
                                  </div>)}
                              </div>
                            </div>
                          </div>}
                      </div>}

                  {}
                  {section.hasPermissions && section.permissionsKey === 'entra-auth' && selections.orgData === 'entra' && <div style={{
    marginTop: '12px'
  }}>
                        <button onClick={() => togglePermissions(section.permissionsKey)} style={{
    background: 'none',
    padding: '6px 12px',
    cursor: 'pointer',
    fontSize: '13px',
    display: 'flex',
    alignItems: 'center',
    gap: '6px'
  }}>
                          {expandedPermissions[section.permissionsKey] ? '▼' : '▶'}{' '}
                          View Required Permissions
                        </button>

                        {expandedPermissions[section.permissionsKey] && <div style={{
    marginTop: '8px',
    padding: '12px',
    fontSize: '13px'
  }}>
                            <div style={{
    marginBottom: '12px'
  }}>
                              <strong>
                                Delegated permissions (Microsoft Graph):
                              </strong>
                              <ul style={{
    margin: '6px 0 0 18px'
  }}>
                                {entraDelegatedPermissions.map(p => <li key={p}>{p}</li>)}
                              </ul>
                            </div>

                            <div>
                              <strong>
                                Application permissions (Microsoft Graph):
                              </strong>
                              <ul style={{
    margin: '6px 0 0 18px'
  }}>
                                {entraApplicationPermissions.map(p => <li key={p}>{p}</li>)}
                              </ul>
                            </div>
                          </div>}
                      </div>}
                </div>
              </div>)}

            <h5>
              {getImplementationSteps().length + 1}. Save These Details for
              Portal Day One
            </h5>
            <div className="ml-4 mb-4">
              <p style={{
    margin: '0 0 8px 0',
    fontSize: '14px'
  }}>
                Keep these credentials secure and accessible:
              </p>
              <ul style={{
    margin: 0,
    paddingLeft: '20px',
    fontSize: '14px'
  }}>
                {config.requirements.map((req, index) => <li key={index}>
                    <strong>{req.component}:</strong> {req.credentials}
                  </li>)}
              </ul>
            </div>
          </div>

          <div style={{
    marginTop: '20px'
  }}>
            <button onClick={resetWizard} style={{
    padding: '8px 16px',
    cursor: 'pointer',
    fontSize: '14px'
  }}>
              ← Start Over
            </button>
          </div>
        </div>}
    </div>;
};

<Tip>
  Spotify Portal brings together your tools, services, documentation, and APIs
  in one unified developer experience. This guide will help you set up Portal
  quickly and start unlocking productivity benefits for your engineering teams.
</Tip>

## Prerequisites

Before you start, make sure you have the following:

* Your Portal domain
* Access to your identity provider (GitHub, Okta, or Entra ID)
* Admin permissions for your chosen identity provider

<InteractiveSetup />

## Supported Identity Providers

<Columns cols={2}>
  <Card title="GitHub" href="/portal/guides/auth/github" cta="GitHub Setup Guide">
    <p>
      Integrate with GitHub for repository access and user authentication.
      Supports both OAuth Apps and GitHub Apps.
    </p>
  </Card>

  <Card title="Okta" href="/portal/guides/auth/okta" cta="Okta Setup Guide">
    <p>
      Enterprise single sign-on with Okta identity provider for secure user
      access and management.
    </p>
  </Card>

  <Card title="Entra ID" href="/portal/guides/auth/entraId" cta="Entra ID Setup Guide">
    <p>
      Microsoft Entra ID (formerly Azure Active Directory) for enterprise
      authentication integration.
    </p>
  </Card>
</Columns>

## Next Steps

After completing authentication setup:

1. **Add organizations and administrators** - Configure your team structure
2. **Access the Setup Guide** - Follow the comprehensive checklist for essential plugins
3. **Personalization** - Use **App Settings** at `/admin/app-settings` to customize your Portal
4. **Onboard your team** - Start adding services and team members to the Software Catalog

<Note>
  After completing the Setup Wizard, you'll gain access to the Setup Guide with:

  * A detailed checklist of essential plugins
  * Step-by-step configuration instructions
  * Links to documentation and troubleshooting
  * Best practices for Portal implementation
</Note>
