How an internal developer portal is put together, where its catalog data comes from, how scorecards and self-service actions work and how it differs from the platform behind it.
An internal developer portal is the web interface engineers open when they need to know which services exist, who owns them, how to call them and how to get a new one into production. It usually starts as a service catalog and grows into scorecards, docs and buttons that create repositories or environments.
Getting one installed takes days, while getting engineers to trust what it shows takes much longer, and that depends almost entirely on where the catalog data comes from. This guide covers the parts of a portal, the data model behind it, how to roll one out and where it stops and the platform begins. For a shorter overview of portal features, see What is a developer portal.
The internal developer platform is what builds, deploys and runs software: CI, the deployment system, infrastructure provisioning, environments and observability. The portal is an interface to that platform. When a developer clicks "Create environment", the portal passes the request to the platform, which does the provisioning and reports back.
That distinction shapes how you plan the work. A portal on top of a thin or inconsistent platform becomes a directory of links, and teams stop opening it. A strong platform with no portal still works through a CLI, Git and dashboards. It usually pays to fix the platform first and add the portal once there is a consistent workflow to expose.
The catalog is the core of every portal, and its data model is worth designing before you import anything. Backstage's system model is a common reference even outside Backstage:
In Backstage, each component is described by a catalog-info.yaml file in its repository, using the descriptor format:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: orders
description: Order intake and fulfillment events
spec:
type: service
lifecycle: production
owner: team-checkout
system: storefront
providesApis:
- orders-api
dependsOn:
- resource:default/orders-db
Descriptor files are easy to start with and hard to keep accurate. The owner field goes stale after a reorg, and dependsOn lists whatever someone remembered when the file was written. The portals engineers trust either generate these facts from code and cloud accounts, or check descriptors against them in CI.
Scorecards evaluate each catalog entry against rules your organization sets: has an owner and on-call rotation, runs a supported runtime version, has alerts configured, passed a security scan in the last 30 days. They turn standards that used to live in a wiki into a visible checklist per service, and platform teams use them to track migrations across hundreds of repositories.
Self-service actions are forms that call the platform. A "New service" template creates a repository from a starter, registers it in the catalog and sets up CI. A "Preview environment" action passes a branch to the deployment system. The portal collects inputs and shows progress, and the platform engineering tools behind it do the work.
Both depend on the data being right, and a scorecard that marks a service as missing alerts when alerts exist under another name teaches teams to ignore scorecards.
Backstage is the open source framework behind a large share of self-built portals. It is a React frontend and Node.js backend your team hosts, with plugins for catalogs, docs, templates and hundreds of integrations. You get full control over the data model and UI, and you take on upgrades, plugin maintenance and the TypeScript code that customizations require.
Hosted portal products run the portal for you and ship integrations and scorecard engines out of the box, in exchange for less control over the model and a vendor in the path. In both cases the integrations are where the effort goes: connecting source control, CI, the deployment system, cloud accounts, incident tooling and the identity provider, then keeping those connections working as tools change. Backstage alternatives compares the options.
Encore combines an open source infrastructure SDK with a platform that provisions and deploys into your own AWS or GCP account. Services, APIs, databases and Pub/Sub topics are declared in application code, and Encore's application model built from those declarations drives the service catalog, API docs, the Flow architecture diagram and the cloud provisioning, so the catalog stays in step with the code that is deployed.

Doc comments become catalog entries, so a Go orders service with its database and an event topic documents itself like this:
Run in your terminal to get started locally.
// Orders accepts orders from the storefront and announces them to
// fulfillment and billing. Owned by team-checkout.
package orders
import (
"context"
"encore.dev/beta/auth"
"encore.dev/beta/errs"
"encore.dev/pubsub"
"encore.dev/storage/sqldb"
)
var db = sqldb.NewDatabase("orders", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
type OrderPlaced struct {
OrderID int64 `json:"order_id"`
}
// Placed is published once per accepted order.
var Placed = pubsub.NewTopic[*OrderPlaced]("order-placed", pubsub.TopicConfig{
DeliveryGuarantee: pubsub.AtLeastOnce,
})
type CreateParams struct {
TotalCents int64 `json:"total_cents"`
}
type CreateResponse struct {
OrderID int64 `json:"order_id"`
}
// Create stores a new order for the authenticated customer and publishes OrderPlaced.
// Totals are in the smallest currency unit.
//
//encore:api auth method=POST path=/orders
func Create(ctx context.Context, p *CreateParams) (*CreateResponse, error) {
uid, ok := auth.UserID()
if !ok {
return nil, &errs.Error{Code: errs.Unauthenticated, Message: "login required"}
}
if p.TotalCents < 1 {
return nil, &errs.Error{Code: errs.InvalidArgument, Message: "total_cents must be >= 1"}
}
var id int64
err := db.QueryRow(ctx, `
INSERT INTO orders (customer_id, total_cents) VALUES ($1, $2)
RETURNING id
`, string(uid), p.TotalCents).Scan(&id)
if err != nil {
return nil, err
}
if _, err := Placed.Publish(ctx, &OrderPlaced{OrderID: id}); err != nil {
return nil, err
}
return &CreateResponse{OrderID: id}, nil
}
Under encore run, the local development dashboard shows the orders service with its documented endpoint, request and response fields and an API explorer for calling it, and Flow draws the service and the order-placed topic with its subscribers, highlighting the database queries when you hover over the service. The same views are available for deployed environments in the Encore dashboard. encore gen client <app-id> --lang=openapi --output=./openapi.json exports an OpenAPI spec, which an existing portal can ingest as the API definition for the component.
On deploy, the database becomes RDS or Cloud SQL and the topic becomes SNS and SQS or GCP Pub/Sub, and Encore provisions an IAM role for the service on AWS or a service account on GCP. The dependency view and the permissions come from the same declarations, which hand-maintained catalogs often lack. Coding agents can read the same model through Encore's MCP server, so an agent working on fulfillment can look up the order-placed schema instead of guessing at it.
Best fit: teams that want an accurate catalog, API docs and dependency map for their backend, generated from the code and deployed into their own AWS or GCP account, either on its own or feeding a wider portal.
Teams that want the catalog and API docs to match the running backend can install Encore and let the service graph, OpenAPI export and dependency views come from the same TypeScript or Go declarations that deploy into their own cloud.
An internal developer portal is a web interface where engineers find the services their company runs, who owns them, their APIs and docs, how they are deployed and which self-service actions they can run. It sits on top of the internal developer platform and the tools that do the actual building and deploying.
The platform is the set of workflows and infrastructure that builds, deploys and runs software. The portal is one interface to it, showing the catalog and exposing actions that call the platform. A platform can work without a portal through a CLI or Git workflow, but a portal without a platform only displays information.
Backstage gives full control and a large plugin ecosystem, but it is a React and Node.js application your team hosts, upgrades and extends. Hosted portals trade some flexibility for less to maintain. Either way, most of the effort goes into keeping catalog data accurate and connecting integrations.
Encore generates its service catalog, API documentation and architecture diagram from the application code, including Go doc comments above //encore:api endpoints. Adding an endpoint, database or Pub/Sub topic updates the catalog on the next encore run or deploy, and encore gen client --lang=openapi exports an OpenAPI spec for other tools.
Deploy to your cloud on AWS and Google Cloud
without managing infrastructure
Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.