Registration architecture
GitLab.com registers and onboards new users after the Unified Lightweight Registration project removed the older, flag-gated registration paths. It covers the controllers and services that run between an anonymous visitor arriving at a sign-up page and that user landing in a project or group.
The SaaS (GitLab.com) registration flow is the primary focus below. Self-managed subscription purchases share some of these services, and the differences are called out explicitly where they apply.
Overview
The flow has five stages: pick an entry point, submit credentials, initialize the account status, optionally verify identity, then run a welcome step that is specific to why the user registered.
flowchart TD
accTitle: GitLab.com registration and onboarding flow
accDescr: An anonymous user picks an entry point, submits credentials, has their account status initialized, optionally completes identity verification on GitLab.com, then is routed through a welcome controller chosen by registration type, ending at a completion path.
A[Anonymous user] --> B{Entry point}
B --> C[Submit credentials]
C --> D[Onboarding::StatusCreateService]
D --> E{Identity verification required?}
E -->|Yes, GitLab.com only| F[RegistrationsIdentityVerificationController]
E -->|No| G[Welcome controller by registration type]
F -->|success| G
G --> H[Completion path]
Entry points
An anonymous user can start registration from several places:
| Entry point | Path | Controller |
|---|---|---|
| Standard sign-up | /users/sign_up | RegistrationsController#new (CE, prepended by EE::RegistrationsController) |
| Invite link | /users/sign_up?invite=... | RegistrationsController#new, using the AcceptsPendingInvitations concern |
| Trial sign-up | /-/trial_registrations/new | TrialRegistrationsController#new (EE) |
| OmniAuth (Google or GitHub) | /users/auth/:provider | OmniAuth request phase, redirects to the provider |
| OmniAuth callback | /users/auth/:provider/callback | OmniauthCallbacksController#handle_omniauth |
| Sign-in page | /users/sign_in | Shows the same social sign-up buttons |
| Subscription purchase | /-/subscriptions/new | GitlabSubscriptions::SubscriptionsController#new |
The social sign-up buttons cover only Google (google_oauth2) and GitHub
(github), listed as POPULAR_PROVIDERS. SAML is available per group as
Enterprise SSO, but it is not one of the self-serve sign-up buttons.
When an anonymous user reaches /-/subscriptions/new, ensure_registered!
calls store_location_for(:user, request.fullpath). After the user
registers, they return to the subscription checkout page instead of the
default completion path.
A self-managed subscription purchase follows a separate branch. The query
parameters deployment_type=self_managed and plan_id trigger the
GitlabSubscriptions::SelfManagedPurchaseRedirect concern, which is included
in both EE::RegistrationsController and
GitlabSubscriptions::SubscriptionsController#new.
Submitting credentials
Once the user submits the sign-up form, one of these actions creates the
User record:
POST /users→RegistrationsController#create, which builds the user throughUsers::RegistrationsBuildService.POST /-/trial_registrations→TrialRegistrationsController#create, which overrides#resourceto build the user throughUsers::AuthorizedBuildServiceinstead.- OmniAuth sign-up →
OmniauthCallbacksController#handle_omniauth. If the OmniAuth identity belongs to an existing user with two-factor authentication enabled,sign_in_user_flowintercepts the request withprompt_for_two_factorinstead of completing sign-in immediately.
Devise and account status initialization
Two pieces of Devise-adjacent logic run right after account creation:
- The
IdentityVerifiableconcern (ee/app/models/concerns/identity_verifiable.rb) overridesactive_for_authentication?to returnfalsefor unverified users on GitLab.com, which forces the user into the identity verification path before they can sign in normally. This only applies whenGitlab::Saas.feature_available?(:identity_verification)is true. Onboarding::StatusCreateServicewrites theuser_details.onboarding_statusJSONB column and setsonboarding_in_progress = true. It recordsregistration_type,initial_registration_type, and theglm_sourceandglm_contentmarketing attribution parameters. This step is skipped for self-managed subscription registrations.
Onboarding as a whole is gated by Onboarding.enabled?, which delegates to
Gitlab::Saas.feature_available?(:onboarding).
Separately, enterprise user association can run at this point too. The
Groups::EnterpriseUsers::Associable concern checks eligibility during
registration, and Groups::EnterpriseUsers::AssociateService can also run
retroactively and asynchronously through AssociateWorker.
Registration type strategy
Onboarding behavior depends on why the user registered. Onboarding::StatusPresenter
delegates to Onboarding::UserStatus, which selects a strategy class using
the REGISTRATION_KLASSES and REGISTRATION_TYPE mappings defined in
ee/app/models/onboarding/user_status.rb. All strategy classes live under
ee/app/models/onboarding/.
| Registration type | Strategy class | Welcome controller | Primary service |
|---|---|---|---|
| Free | FreeRegistration | Registrations::WelcomeController | Onboarding::FreeNamespaceCreateService |
| Trial | TrialRegistration | Registrations::TrialWelcomeController | Onboarding::TrialNamespaceCreateService |
| Automatic trial | AutomaticTrialRegistration (subclass of TrialRegistration) | Registrations::TrialWelcomeController | Onboarding::TrialNamespaceCreateService |
| Invite | InviteRegistration | Registrations::InviteWelcomeController | Users::InviteSignupService |
| Subscription | SubscriptionRegistration | Registrations::SubscriptionWelcomeController | Onboarding::SubscriptionNamespaceCreateService, then Onboarding::FinishService |
| Subscription, self-managed | SubscriptionSmRegistration | None. Onboarding::StatusCreateService is skipped for this type. | None |
AutomaticTrialRegistration covers the free-to-trial conversion. A user
registers as free, but the current registration_type becomes trial
while initial_registration_type stays free for analytics. This
conversion happens when a free user who selected “for my company” is routed
by FreeAutomaticTrialConstraint to Registrations::TrialWelcomeController.
Onboarding::TrialNamespaceCreateService then sets registration_type to
trial, which is why Onboarding::UserStatus resolves the strategy to
AutomaticTrialRegistration rather than plain TrialRegistration.
Identity verification (GitLab.com only)
Identity verification runs at GET /users/identity_verification, handled by
RegistrationsIdentityVerificationController#show.
The /-/users/identity_verification path is handled by a different,
unrelated controller used for reverifying an already-signed-in user. Do not
confuse the two when tracing a registration issue.
Which verification methods are required depends on a risk score:
| Risk level | Required methods |
|---|---|
| Low | |
| Medium | Email, phone |
| High | Email, phone, credit card |
| Assumed-high | Email, credit card, phone |
Each method is backed by its own service:
- Email: the
verify_email_codeaction callsUsers::EmailVerification::ValidateTokenService. - Phone: the
send_phone_verification_codeandverify_phone_verification_codeactions callPhoneVerification::Users::SendVerificationCodeServiceandPhoneVerification::Users::VerifyCodeService. Both delegate toPhoneVerification::TelesignClient::*, which talks to the Telesign vendor API. - Credit card: the
verify_credit_card(GET) andverify_credit_card_captcha(POST) actions have no single dedicated service. Verification relies oncredit_card_validationandUsers::AutoBanService. The Zuora hosted payment form is embedded in the frontend and brokered through CustomersDot usingGitlab::SubscriptionPortal::Client#payment_form_paramsand#validate_payment_method.Users::UpsertCreditCardValidationServicepersists the result, includingzuora_payment_method_xidand the associated Stripe fields.
Users::BaseIdentityVerificationController also enforces an Arkose
challenge through Arkose::TokenVerificationService as an anti-abuse check
on this path.
When verification succeeds, the success action:
- Accepts any pending invitations.
- Signs the user in.
- Calls
Onboarding::TriggerAccountCreatedIterableService. - Resolves the redirect (see Completion paths).
Onboarding::StatusConvertToInviteService only runs as part of that
pending-invitations step, and only when invitations actually exist.
Welcome controllers and services
After credentials are submitted and, where required, identity is verified,
a welcome controller finishes setting up the account. These are the current
controllers and services; earlier iterations of this flow used
Users::SignupService and Users::SubscriptionSignupService, both of which
were removed once the feature flags they supported were cleaned up.
Registrations::WelcomeController#updatecallsOnboarding::FreeNamespaceCreateService.Registrations::TrialWelcomeController#updatecallsOnboarding::TrialNamespaceCreateService, which creates the sales lead throughGitlabSubscriptions::CreateLeadService. This controller is served at/users/sign_up/welcomeunder both theTrialUserConstraintand theFreeAutomaticTrialConstraint.Registrations::InviteWelcomeController#updatecallsUsers::InviteSignupService.Registrations::SubscriptionWelcomeController#updatecallsOnboarding::SubscriptionNamespaceCreateService, thenOnboarding::FinishService, then redirects tousers_sign_up_customers_portal_redirect_path.Registrations::CompanyController(/users/sign_up/company,newandcreate) callsOnboarding::StatusStepUpdateServiceandGitlabSubscriptions::CreateCompanyLeadService.Registrations::GroupsController(createandimport) callsOnboarding::FinishService.Registrations::CustomersPortalRedirectController#showis the final leg for subscription and trial users. It redirects to CustomersDot withauto_submit_sso=true.
Completion paths
Where a user lands once onboarding finishes depends on their registration type:
| Registration type | Destination |
|---|---|
| Free | project_learn_gitlab_path(project), or new_project_path when the remove_onboarding_tutorial_pages flag is enabled |
| Trial | namespace_project_get_started_path |
| Subscription | stored_location_for(:user), which resolves to the subscription checkout page through the CustomersDot redirect |
| Invite | The invited project or group, through polymorphic_path(last_invited_member_source) |
| None of the above | /dashboard/projects |
External systems
Registration and onboarding depend on several systems outside the main GitLab.com Rails application:
| System | Used for |
|---|---|
| CustomersDot | Leads and subscriptions, through Gitlab::SubscriptionPortal::Client |
| Arkose | Anti-abuse challenges during identity verification |
| Telesign | Phone number verification |
| Zuora, through CustomersDot | Credit card verification, including stored Stripe fields |
| Email or SMTP | Confirmation instructions and verification codes |
| Redis | Session state and stored_location_for redirects |
| PostgreSQL | The user_details.onboarding_status JSONB column |
Tracking
Registration and onboarding capture data for attribution and conversion analysis:
glm_sourceandglm_contentquery parameters are stored ononboarding_statuswhen the account status is created.- Registration and identity verification steps emit Snowplow and internal product-analytics events.
- Conversion analysis distinguishes
initial_registration_typefrom the currentregistration_type, which is how the free-to-trial conversion described in Registration type strategy is measured.