> For the complete documentation index, see [llms.txt](https://docs.carto.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.carto.com/carto-for-developers/guides/build-a-hosted-application.md).

# Hosting your application in CARTO

Build an application that CARTO hosts and serves behind your organization's login

{% hint style="info" %}
**Private Preview:** Hosted Apps is available to selected accounts during its Private Preview. To request access, contact your CARTO representative.
{% endhint %}

Here you will learn how to build an application that CARTO hosts for you with [Hosted Apps](/carto-for-developers/key-concepts/hosting-your-application.md). Like a [private application](/carto-for-developers/guides/build-a-private-application.md), it will only be accessible to users inside your CARTO organization, but CARTO serves the files and signs users in, so there is no OAuth client to create and no login code to write. If you only want to see a hosted application working first, follow [Hosting your first application](/carto-for-developers/quickstart/hosting-your-first-application.md).

After completing this guide you will be familiar with the following concepts:

* Preparing your application to be hosted by CARTO.
* Reading the user's credentials at runtime.
* Deploying and sharing your application.
* Using Named Sources, so that no SQL reaches the browser.
* Publishing new versions and rolling back.

{% hint style="info" %}
During this guide, we're using the [CARTO Data Warehouse](/carto-user-manual/connections/carto-data-warehouse.md). The process explained here is also compatible with other Warehouses like BigQuery, Snowflake, Redshift, Databricks, Oracle, or Postgres. Instead of using <mark style="color:orange;">`connectionName: 'carto_dw'`</mark>, you need to use the name of your connection.
{% endhint %}

## Before you start

* Install the [CARTO CLI](/carto-for-agents/cli/installation.md), version 0.12.0 or later, and log in with `carto auth login`. You will use the CLI to deploy the application, and its login to run the application on your computer.
* Make sure Hosted Apps is enabled for your account (see the note above).

## Scaffolding your application

As in the other guides, we're going to start from the basic CARTO template in [Vite](https://vitejs.dev/), which already includes a basemap and deck.gl:

```sh
git clone https://github.com/CartoDB/carto-for-developers-guides.git
cp -r carto-for-developers-guides/template hosted-app
cd hosted-app
npm install
```

Then update the `scripts` in <mark style="color:orange;">package.json</mark>. `build` produces the files you will deploy in the `dist` folder, and `deploy` builds and deploys them in one step:

```json
"scripts": {
  "dev": "vite --https --open",
  "build": "tsc && vite build",
  "deploy": "npm run build && carto app deploy dist --name \"Retail stores\" --slug retail-stores",
  "preview": "vite preview",
  "lint": "eslint . --ext .ts"
}
```

The slug, `retail-stores`, identifies your application and is part of its URL.

## Preparing the application for CARTO hosting

CARTO serves a hosted application under `/app/<slug>/` instead of at the root of a domain, and gives it the user's credentials in a file named `carto-info.json`. Replace <mark style="color:orange;">vite.config.js</mark> with the following, which does two things:

* `base: './'` makes every asset URL relative, so the files load correctly under `/app/<slug>/`.
* The `cartoInfo` plugin serves `carto-info.json` while you develop, using your CARTO CLI login. Once the application is deployed, CARTO serves this file instead, with the credentials of each user.

```javascript
import basicSsl from '@vitejs/plugin-basic-ssl'
import eslint from 'vite-plugin-eslint'
import { readFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'

// Development only: serve carto-info.json from your CARTO CLI login, as CARTO
// serves it to each user once the app is deployed.
function cartoInfo() {
  return {
    name: 'carto-info',
    apply: 'serve',
    configureServer(server) {
      server.middlewares.use('/carto-info.json', (req, res) => {
        const credentials = JSON.parse(readFileSync(join(homedir(), '.carto_credentials.json'), 'utf-8'))
        const profile = credentials.profiles[process.env.CARTO_PROFILE || credentials.current_profile]
        res.setHeader('Content-Type', 'application/json')
        res.setHeader('Cache-Control', 'no-store')
        res.end(JSON.stringify({
          schemaVersion: 1,
          accessToken: profile.token,
          apiBaseUrl: process.env.CARTO_API_BASE_URL || `https://${profile.tenant_id}.api.carto.com`,
          user: { id: 'local', accountId: profile.organization_id, email: profile.user_email }
        }))
      })
    }
  }
}

export default {
  base: './',
  plugins: [basicSsl(), eslint(), cartoInfo()]
}
```

The plugin uses the API Base URL of your CARTO region. If the one shown in Workspace -> Developers is different, start the development server with `CARTO_API_BASE_URL` set to it.

## Reading the user's credentials

A hosted application does not store any credential. When it starts, it fetches `carto-info.json`, relative to its own URL, and CARTO answers with the access token of the user who opened it and the API base URL to use. The [runtime and authentication reference](/carto-for-developers/reference/hosted-apps/runtime-and-authentication.md) describes every field.

Create a file <mark style="color:orange;">src/session.ts</mark>:

```typescript
export interface CartoSession {
  accessToken: string;
  apiBaseUrl: string;
  expiresAt?: number;
  user: { id: string; accountId: string; email: string | null } | null;
}

export async function getSession(): Promise<CartoSession> {
  const response = await fetch('./carto-info.json', { cache: 'no-store' });
  if (response.status === 401) {
    renewSession();
  }
  if (!response.ok) {
    throw new Error(`carto-info.json returned HTTP ${response.status}`);
  }
  return response.json();
}

export function renewSession() {
  const slug = location.pathname.split('/')[2];
  location.assign(`/app/_session?slug=${slug}`);
}
```

CARTO keeps the user's session in the application for up to one hour. When it ends, `carto-info.json` returns `401`, and `renewSession` takes the user through the CARTO login and back to the application. If they are still logged in to CARTO, this happens without asking them for anything. Call `renewSession` too if a CARTO API returns `401`.

## Visualizing a dataset

Now let's use those credentials to add a layer. Replace <mark style="color:orange;">src/map.ts</mark> with:

```typescript
import maplibregl from 'maplibre-gl';
import { Deck } from '@deck.gl/core';
import { BASEMAP, vectorTableSource, VectorTileLayer } from '@deck.gl/carto';
import type { CartoSession } from './session';

export function createMap({ apiBaseUrl, accessToken }: CartoSession) {
  const stores = vectorTableSource({
    apiBaseUrl,
    accessToken,
    connectionName: 'carto_dw',
    tableName: 'carto-demo-data.demo_tables.retail_stores'
  });

  const deck = new Deck({
    canvas: 'deck-canvas',
    initialViewState: { latitude: 39.8097343, longitude: -98.5556199, zoom: 4 },
    controller: true,
    layers: [
      new VectorTileLayer({
        id: 'stores',
        data: stores,
        pointRadiusMinPixels: 3,
        getFillColor: [200, 0, 80]
      })
    ]
  });

  // Add basemap
  const map = new maplibregl.Map({ container: 'map', style: BASEMAP.POSITRON, interactive: false });
  deck.setProps({
    onViewStateChange: ({ viewState }) => {
      const { longitude, latitude, ...rest } = viewState;
      map.jumpTo({ center: [longitude, latitude], ...rest });
    }
  });
}
```

And <mark style="color:orange;">src/main.ts</mark> with:

```typescript
import './style.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import { createMap } from './map';
import { getSession } from './session';

document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
  <div id="map"></div>
  <canvas id="deck-canvas"></canvas>
`;

getSession().then((session) => createMap(session));
```

The only difference with any other CARTO for Developers application is where the credentials come from. Run `npm run dev` and you should see the retail stores on the map at <https://127.0.0.1:5173/>, loaded with your own credentials.

## Deploying and sharing your application

Deploy the application:

```sh
npm run deploy
```

The CLI uploads the contents of `dist` and prints the URL of your application, ending in `/app/retail-stores/`. Open it in your browser: CARTO asks you to log in if you aren't already, and the application loads with your credentials.

New applications are private. To share it with everyone in your organization, run:

```sh
carto app share retail-stores --org
```

You can also share it with specific groups or users with `--group` and `--user`. Users open the same URL and log in with their CARTO account. The application runs with their credentials, so each of them only sees the data they have access to.

## Using Named Sources

Right now, the application tells CARTO which table to read, and a SQL query would travel from the browser in the same way. With [Named Sources](/carto-for-developers/guides/avoid-exposing-sql-queries-with-named-sources.md), the query is stored in CARTO and the application only references it by name, so no SQL reaches the browser.

A hosted application declares its Named Sources in a `carto.json` file at the root of the deployed files. Create <mark style="color:orange;">public/carto.json</mark> (Vite copies the `public` folder to the root of `dist`):

```json
{
  "connections": [
    {
      "name": "carto_dw",
      "provider": "bigquery",
      "sources": {
        "stores_in_state": {
          "sql": "SELECT cartodb_id, storetype, revenue, geom FROM `carto-demo-data`.demo_tables.retail_stores WHERE state = @state"
        }
      }
    }
  ]
}
```

When you deploy, CARTO registers each source under the name `<slug>_<key>`, here `retail-stores_stores_in_state`. The CLI does not upload `carto.json`, so it is never served to the browser.

Then, in <mark style="color:orange;">src/map.ts</mark>, replace `vectorTableSource` with `vectorQuerySource` in the import, and the source with:

```typescript
const stores = vectorQuerySource({
  apiBaseUrl,
  accessToken,
  connectionName: 'carto_dw',
  sqlQuery: 'retail-stores_stores_in_state',
  queryParameters: { state: 'CA' }
});
```

Deploy again with `npm run deploy`. The map now shows only the stores in California, and the network requests carry the name of the source and its parameters, never its SQL. Named Sources run with the credentials of the user, like any other request of the application. From this point, they also work with `npm run dev`.

## Publishing new versions

Each `npm run deploy` publishes a new version of the application and makes it the active one. The URL and the sharing settings stay the same. To list the versions and go back to a previous one:

```sh
carto app versions retail-stores
carto app rollback retail-stores <version>
```

A rollback changes the files that are served. Named Sources stay as they were in the last deploy.

**Congratulations!** You now have an application that CARTO hosts and serves to the users you share it with, with their own credentials and without exposing any SQL.

## What's next?

* Learn how hosting fits with the rest of your options in [Hosted Apps](/carto-for-developers/key-concepts/hosting-your-application.md), in Key concepts.
* The [Hosted Apps reference](/carto-for-developers/reference/hosted-apps.md) describes the [`carto.json` manifest](/carto-for-developers/reference/hosted-apps/app-manifest.md), the [runtime and authentication contract](/carto-for-developers/reference/hosted-apps/runtime-and-authentication.md), and the [bundle requirements and limits](/carto-for-developers/reference/hosted-apps/bundle-requirements-and-limits.md), including which external resources an application can load.
* The [`app` command reference](/carto-for-agents/cli/command-reference/app.md) documents every command and flag, including `carto app delete`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.carto.com/carto-for-developers/guides/build-a-hosted-application.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
