# Introduction ## The new Nuxt Studio module When NuxtLabs joined Vercel, the team promised to release Nuxt Studio as a free open-source, self-hosted module for Nuxt projects. ::u-button --- color: neutral icon: i-simple-icons-github target: _blank to: https://github.com/nuxt-content/nuxt-studio variant: subtle --- Discover the Nuxt Studio module on GitHub. :: That promise is now fulfilled. Thanks to vercel support the first version of the **Nuxt Studio module** is available. You can now enable **content editing directly in production**, with real-time preview and GitHub integration, all from within your own Nuxt application. ::u-button --- color: neutral external: "" icon: i-lucide-mouse-pointer-click to: https://nuxt.studio/admin?redirect=/introduction --- Try editing this page :: ## How does it differ from the standalone platform? Originally offered as a hosted platform, Studio has evolved into an open-source Nuxt module that you can deploy alongside your Nuxt Content website. This means content editors can manage and update content directly in production, without needing local development tools or Git knowledge. ### Key differences - ✅ **Self-hosted** — runs entirely on your own infrastructure - ✅ **No external dependencies** — no APIs or third-party services required - ✅ **Free and open-source** — released under the MIT license - ✅ **Built-in integration** — works within your Nuxt app ::warning The new Nuxt Studio module requires a server-side route for authentication. While static generation remains supported, your site must be **deployed on a platform that supports server-side rendering (SSR)** . :: # Setup Nuxt Studio ## Installation ::warning{to="https://content.nuxt.com"} Nuxt Studio allows content edition of Nuxt Content websites only. :: Install Nuxt Studio using the Nuxt CLI within your project: ```bash [Terminal] npx nuxt module add nuxt-studio ``` ::tip{icon="i-lucide-rocket"} Start your development server and start editing your Nuxt Content website. :: ## Development mode Previous section is enough to be able to edit your content. Just click on the floating button on the bottom left of your page. When running locally, **any file changes will be synchronized in real time with your local filesystem**. ::note The publish system is only available in production mode. Use your current workflow (git command, IDE, GitHub Desktop...) to commit your changes. :: ## Production mode Studio's main advantage is publishing content changes directly from your production website. This requires two configurations: ::steps ### Git Provider Configure where your content is stored and where changes will be committed. When deploying on **Vercel**, **Netlify**, **GitHub Actions**, or **GitLab CI**, the repository details (`provider`, `owner`, `repo`, and `branch`) are **automatically detected** from the CI environment variables — no manual configuration is needed. :::note On Vercel, make sure that the **[Automatically expose System Environment Variables](https://vercel.com/docs/environment-variables/system-environment-variables#automatically-expose-system-environment-variables){rel=""nofollow""}** option is enabled in your project settings for auto-detection to work. ::: For other platforms, or to override the detected values, configure the repository manually: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { provider: 'github', // 'github' or 'gitlab' owner: 'your-username', repo: 'your-repo', branch: 'main' } } }) ``` :::tip{to="https://nuxt.studio/git-providers"} Learn more about GitHub and GitLab providers. ::: ### Auth Provider Configure how users authenticate to access Studio. Choose from GitHub, GitLab, Google OAuth, or custom authentication: ```bash [.env] # Example with GitHub OAuth NUXT_STUDIO_AUTH_GITHUB_CLIENT_ID= NUXT_STUDIO_AUTH_GITHUB_CLIENT_SECRET= ``` :::tip{to="https://nuxt.studio/auth-providers"} Follow the complete setup instructions for your auth provider. ::: ### Deployment Nuxt Studio requires a server-side route for authentication. While static generation remains supported with [Nuxt hybrid rendering](https://nuxt.com/docs/4.x/guide/concepts/rendering#hybrid-rendering){rel=""nofollow""}, your site must be **deployed on a platform that supports server-side rendering (SSR)** using `nuxt build` command. If you want to pre-render all your pages, use the following configuration: ```ts [nuxt.config.ts] export default defineNuxtConfig({ nitro: { prerender: { // Pre-render the homepage routes: ['/'], // Then crawl all the links on the page crawlLinks: true } } }) ``` ### Open Studio Once deployed, open Studio by navigating to your configured route (default: `/_studio`): 1. Click the login button if it does not directly redirect to the OAuth app authorization page 2. Authorize the OAuth application 3. You'll be redirected back to Studio ready to edit your content :::tip You can also use the shortcut `CMD` \+ `.` to redirect to the Studio route. ::: :: ## Options Add the module to your `nuxt.config.ts` and configure your repository based on your Git provider: ### Admin route Customize the login route using the `route` option: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { route: '/admin', // default: '/_studio' } }) ``` ### Repository Use the `repository` option to specify your git repository to sync in production mode. When deploying on supported platforms, the repository details are **automatically detected** from CI environment variables: | Platform | Provider | Owner | Repo | Branch | | ------------------ | --------------------- | ------------------------ | ------------------------ | ----------------------- | | **Vercel** | `VERCEL_GIT_PROVIDER` | `VERCEL_GIT_REPO_OWNER` | `VERCEL_GIT_REPO_SLUG` | `VERCEL_GIT_COMMIT_REF` | | **Netlify** | from `REPOSITORY_URL` | from `REPOSITORY_URL` | from `REPOSITORY_URL` | `BRANCH` | | **GitHub Actions** | `github` | from `GITHUB_REPOSITORY` | from `GITHUB_REPOSITORY` | `GITHUB_REF_NAME` | | **GitLab CI** | `gitlab` | `CI_PROJECT_NAMESPACE` | `CI_PROJECT_NAME` | `CI_COMMIT_BRANCH` | Auto-detection applies to all fields, including `branch`. CI-detected values take precedence over manually configured values in `nuxt.config.ts`. To override or for unsupported platforms, configure the repository manually: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { provider: 'github', // 'github' or 'gitlab', default: 'github' owner: 'your-username', // your GitHub/GitLab username or organization repo: 'your-repo', // your repository name branch: 'main', // the branch to commit to (default: main) } } }) ``` #### Instance URL For GitHub Enterprise or self-hosted GitLab, you can specify the instance URL. Default value is based on the provider: - **GitHub**: `https://github.com` - **GitLab**: `https://gitlab.com` ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { ... instanceUrl: 'https://.company.com' } } }) ``` ::note Can be overridden by the `NUXT_STUDIO_AUTH_GITHUB_INSTANCE_URL` or `NUXT_STUDIO_AUTH_GITLAB_INSTANCE_URL` environment variable. :: #### Root directory `default: ''` If your Nuxt Content application is in a monorepo or subdirectory, specify the `rootDir` option to point to the correct location. ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { ... rootDir: 'docs' } } }) ``` #### Private Repository Access `default: true` By default, Studio requests access to both public and private repositories. Setting `private: false` limits the OAuth scope to public repositories only, which may be preferable for security or compliance reasons when working with public repositories. ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { ... private: false } } }) ``` ### Internationalization Nuxt Studio includes built-in internationalization support with the following languages available: - 🇬🇧 **English** (default) - 🇸🇦 **Arabic** - 🇧🇬 **Bulgarian** - 🇩🇪 **German** - 🇪🇸 **Spanish** - 🇮🇷 **Farsi** - 🇫🇮 **Finnish** - 🇫🇷 **French** - 🇮🇩 **Indonesian** - 🇮🇹 **Italian** - 🇯🇵 **Japanese** - 🇳🇱 **Dutch** - 🇵🇱 **Polish** - 🇧🇷 **Portuguese (Brazil)** - 🇺🇦 **Ukrainian** - 🇨🇳 **Chinese** - 🇰🇷 **Korean** - 🇨🇿 **Czech** - 🇳🇴 **Norwegian (Bokmål)** - 🇳🇴 **Norwegian (Nynorsk)** - 🇷🇺 **Russian** - 🇹🇼 **Chinese (Traditional, Taiwan)** - 🇰🇭 **Khmer** - 🇸🇰 **Slovak** - 🇭🇺 **Hungarian** Set your preferred language using the `i18n` option: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { i18n: { defaultLocale: 'fr' // 'en', 'fr' or 'de' } } }) ``` This will translate: - All UI elements and labels - Monaco editor snippets and code completion - Contextual messages and notifications ::callout{icon="i-lucide-heart-handshake"} Community contributions for new language translations are welcome! If you'd like to add support for a new language, please visit the [GitHub repository](https://github.com/nuxt-content/studio){rel=""nofollow""} and drop a pull request. :: ### Dev mode If you want to test your production setup locally, disable the `dev` option: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { dev: false } }) ``` Make sure to configure your OAuth provider to redirect to your local dev server (usually {rel=""nofollow""}). ### Editor You can customize the appearance and behavior of Nuxt Studio by configuring the `editor` object in your `nuxt.config.ts` file #### Filter components You can control which components are visible and how they are displayed in Nuxt Studio using the `editor.components` option in your `nuxt.config.ts`. This is useful if you want to hide specific components or only show a subset. ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { editor: { components: { // White-list: If defined, ONLY these components will be visible include: ['Content*', 'MySpecificComponent'], // Black-list: These components will be hidden exclude: ['HiddenComponent', 'content/prose/**'] } } } }) ``` Patterns support glob syntax (`*`, `**`) and can match against: - **Component Name**: e.g. `Button`, `Content*` - **File Path**: If the pattern contains a `/`, e.g. `content/prose/**` #### Organize Components You can organize components into labeled groups in the editor's component picker using the `editor.components.groups` option. This is useful when your project has many components and you want to structure them by category. ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { editor: { components: { groups: [ { label: 'Content', include: ['content*'] }, { label: 'Landing', include: ['landing*'] }, { label: 'UI', include: ['app/components/ui/**'] }, ], ungrouped: 'include', } } } }) ``` - **groups**: Array of `{ label, include }`. Each group appears as a separate section in the slash menu. - **ungrouped**: `'include'` (default) shows unmatched components in a fallback group; `'omit'` hides them. #### Icon libraries Restrict every Studio icon picker (collection forms and Vue component props in the visual editor) to a specific set of [Iconify](https://icones.js.org/){rel=""nofollow""} libraries using `editor.iconLibraries`. Values are Iconify collection prefixes. ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { editor: { iconLibraries: ['material-symbols', 'lucide'], }, }, }) ``` Per-field `iconLibraries` in `.editor()` overrides this global list when set — see [Icon field option](https://nuxt.studio/content#iconlibraries-arraystring). #### Commands The `/` menu includes built-in TipTap sections (**Style** and **Insert**) for headings, lists, marks, images, and so on. Use `editor.commands.exclude` to hide specific built-in entries: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { editor: { commands: { exclude: ['blockquote', 'horizontalRule', 'video'], }, }, }, }) ``` Pass `'style'` or `'insert'` to remove an entire section at once: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { editor: { commands: { exclude: ['style', 'insert'], }, }, }, }) ``` Supported keys: `style`, `insert` (whole sections), plus `paragraph`, `heading1`–`heading4`, `bulletList`, `orderedList`, `blockquote`, `codeBlock`, `bold`, `italic`, `strike`, `code`, `image`, `video`, `horizontalRule`. ### Git You can configure the Git behavior of Nuxt Studio by configuring the `git` object in your `nuxt.config.ts` file #### Commit message prefix Content editors may not be familiar with conventional commit format. You can configure a prefix to prepend to all commit messages for Commitizen/CI compatibility: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { git: { commit: { // Prefix to prepend (include trailing colon for conventional format) messagePrefix: 'content:' // e.g. 'docs:', 'feat:', 'chore:' } } } }) ``` When set, user-entered messages like "Add 2 links on landing page" become "content: Add 2 links on landing page". # Git Providers Git providers handle the synchronization between Studio and your repository. They are responsible for **pushing content changes** (commits) to your Git repository when you publish from Studio. ::note{to="https://nuxt.studio/auth-providers"} Git providers are distinct from **Auth providers** . Git providers determine where your repository is hosted and enable you to publish content changes, while Auth providers control how users authenticate and gain access to Studio. :: ## Publication Requirements To publish content changes to your repository, Studio needs a valid access token with write permissions. The token can come from two sources: ### OAuth-based Access (Automatic) When using **GitHub OAuth** or **GitLab OAuth** as your [Auth provider](https://nuxt.studio/auth-providers), the OAuth token obtained during authentication is automatically used for Git operations. No additional configuration is needed. ```bash [.env] # GitHub OAuth - token is obtained automatically during login NUXT_STUDIO_AUTH_GITHUB_CLIENT_ID= NUXT_STUDIO_AUTH_GITHUB_CLIENT_SECRET= # Or GitLab OAuth - token is obtained automatically during login NUXT_STUDIO_AUTH_GITLAB_APPLICATION_ID= NUXT_STUDIO_AUTH_GITLAB_APPLICATION_SECRET= ``` ### Personal Access Token (Manual) When using **Google OAuth** or **Custom Auth** as your Auth provider, you must provide a Personal Access Token (PAT) with repository write permissions: ```bash [.env] # For GitHub repositories NUXT_STUDIO_GIT_GITHUB_TOKEN= # For GitLab repositories NUXT_STUDIO_GIT_GITLAB_TOKEN= ``` ::tip Check the section below to have more info about how to create a PAT for GitHub or GitLab provider. :: ## Automatic Detection When deploying on **Vercel**, **Netlify**, **GitHub Actions**, or **GitLab CI**, Studio automatically detects the repository `provider`, `owner`, `repo`, and `branch` from the platform's environment variables. This means you can skip the `studio.repository` configuration entirely on these platforms — just set up your [Auth provider](https://nuxt.studio/auth-providers) and deploy. ::note Auto-detection applies to all fields, including `branch` . CI-detected values take precedence over manually configured values in `nuxt.config.ts` . To use a specific branch, unset the corresponding CI environment variable (e.g. `GITHUB_REF_NAME` ) or configure your deployment platform accordingly. :: ::warning On Vercel, you must enable **[Automatically expose System Environment Variables](https://vercel.com/docs/environment-variables/system-environment-variables#automatically-expose-system-environment-variables){rel=""nofollow""}** in your project settings ( **Settings → Environment Variables** ) for auto-detection to work. :: ## Supported Providers Studio supports two Git providers for repository operations: GitHub and GitLab. ### GitHub When deploying on Vercel or Netlify with a GitHub repository, the configuration below is automatically detected. You can also configure it manually in `nuxt.config.ts`: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { provider: 'github', owner: 'your-username', repo: 'your-repo', branch: 'main' // Optional, defaults to 'main' } } }) ``` #### Creating a GitHub Personal Access Token ::warning This section is required if you're using an [Auth provider](https://nuxt.studio/auth-providers) that doesn't provide Git access (like Google OAuth or Custom Auth). In this case you'll need to create a Personal Access Token to publish your changes. :: ::steps{level="4"} #### Navigate to GitHub Token Settings Go to [GitHub Settings → Personal access tokens](https://github.com/settings/personal-access-tokens/new){rel=""nofollow""} and create a new **Fine-grained Personal Access Token**. #### Configure the GitHub Token Fill in the required fields: - **Token name**: Your app name - **Resource owner**: The GitHub organization (or user) the repository belongs to - **Repository access**: Select **Only select repositories** and choose your repository - **Permissions**: Click **Add permission** and select **Contents** then update access to **Read and write** #### Set GitHub Environment Variable Add the token to your deployment platform's environment variables: ```bash [.env] NUXT_STUDIO_GIT_GITHUB_TOKEN= ``` :: ### GitLab When deploying on GitLab CI, the configuration below is automatically detected. You can also configure it manually in `nuxt.config.ts`: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { provider: 'gitlab', owner: 'your-username', // or group name repo: 'your-repo', branch: 'main' // Optional, defaults to 'main' } } }) ``` ::note **Self hosted GitLab**: - You can use the `NUXT_STUDIO_AUTH_GITLAB_INSTANCE_URL` to override the default GitLab instance URL if you're using a self-hosted instance. - [Oauth instance URL](https://nuxt.studio/auth-providers#set-gitlab-environment-variables) is based on the same env variable by default. :: #### Creating a GitLab Personal Access Token ::warning This section is required if you're using an [Auth provider](https://nuxt.studio/auth-providers) that doesn't provide Git access (like Google OAuth or Custom Auth). In this case you'll need to create a Personal Access Token to publish your changes. :: ::steps{level="4"} #### Navigate to GitLab Token Settings Go to **User Settings → Personal access tokens** (or your group/organization settings if applicable) on GitLab. #### Configure the GitLab Token Fill in the required fields: - **Name**: Your app name - **Expiration date**: Set according to your security policy (GitLab defaults to 365 days, and non-expiring tokens are not allowed on most instances). See [GitLab's guidance on expiry limits](https://docs.gitlab.com/user/profile/personal_access_tokens/){rel=""nofollow""}. - **Scopes**: `api` (required for reading/writing repository content) :warning[Copy the generated token immediately; you won't be able to see it again.] #### Set GitLab Environment Variable Add the token to your deployment platform's environment variables: ```bash [.env] NUXT_STUDIO_GIT_GITLAB_TOKEN= ``` :: ## Working with Branches By default, Studio commits changes to the branch specified in your configuration (typically `main`). However, you can configure Studio to work with a staging or preview branch instead. This is useful when you want to review changes on a preview environment before merging to production. ::steps ### Configure Your Branch Update your `nuxt.config.ts` to target your staging branch. :::tip Set `NUXT_PUBLIC_STUDIO_REPOSITORY_BRANCH` at deployment time to configure the branch per environment without rebuilding. ::: ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { repository: { owner: 'your-username', repo: 'your-repo', branch: 'main' // override at runtime via NUXT_PUBLIC_STUDIO_REPOSITORY_BRANCH } } }) ``` ### Deploy Your Staging Environment Configure your hosting platform to deploy the staging branch to a preview URL (e.g., `staging.yourdomain.com`). ### Configure Auth Provider for Staging Create a new OAuth App specifically for your staging environment with your staging URL as callback URL. See [Auth Providers](https://nuxt.studio/auth-providers) for setup instructions. ### Set Environment Variables Configure your staging deployment environment variables depending on the Git and Auth provider you are using. ### Access Studio on Staging Navigate to `https://staging.yourdomain.com/_studio` to edit content. All commits will be pushed to your configured staging branch. ### Merging to Production Once you're satisfied with changes on your staging branch, create a pull request from your staging branch to your main branch to deploy to production. :: # Auth Providers Auth providers handle **user authentication** and access control for Studio. They determine who can log in and edit content. ::note Auth providers are different from [Git providers](https://nuxt.studio/git-providers) . Auth providers handle user authentication, while Git providers handle repository operations. :: ## Provider Comparison | Feature | GitHub OAuth | GitLab OAuth | Google OAuth | SSO Server | Custom Auth | | --------------------- | ------------------------- | ------------------------- | --------------------- | ----------------------- | -------------- | | **Authentication** | ✅ | ✅ | ✅ | ✅ | ✅ | | **Git Operations** | ✅ Automatic (OAuth token) | ✅ Automatic (OAuth token) | ⚠ Requires PAT | ✅ Automatic (SSO token) | ⚠ Requires PAT | | **Access Control** | ✅ OAuth scope | ✅ OAuth scope | ⚠ Moderator whitelist | ✅ SSO server | ⚠ Custom logic | | **Secured Auth Flow** | ✅ Provider-managed | ✅ Provider-managed | ✅ Provider-managed | ✅ SSO-managed | ⚠ Self-managed | ::note You can use multiple providers simultaneously. For example, use GitHub OAuth for developers and Google OAuth for non-technical content editors. :: ## Built-in Providers ::tip Once deployed with the appropriate credentials set as environment variables, Studio will be accessible from your production instance. Navigate to `/_studio` (or your configured route) to start editing and publishing content. :: ### GitHub GitHub OAuth provides authentication with automatic Git access. Users who authenticate via GitHub can immediately push changes to the repository. ::steps{level="4"} #### Navigate to GitHub Developer Settings Go to [GitHub Developer Settings](https://github.com/settings/developers){rel=""nofollow""} and click **New OAuth App** #### Configure the GitHub OAuth Application Fill in the required fields: - **Application name**: Your app name - **Homepage URL**: `https://yourdomain.com` - **Authorization callback URL**: `https://yourdomain.com/__nuxt_studio/auth/github` :::note For local development, add: `http://localhost:3000/__nuxt_studio/auth/github` ::: #### Copy Your GitHub Credentials After creating the OAuth app, you'll receive: - A **Client ID** (visible immediately) - A **Client Secret** (click **Generate a new client secret**) #### Set GitHub Environment Variables Add the GitHub OAuth credentials to your deployment platform's environment variables or `.env` file: ```bash [.env] NUXT_STUDIO_AUTH_GITHUB_CLIENT_ID= NUXT_STUDIO_AUTH_GITHUB_CLIENT_SECRET= # Optional: Restrict access to specific users # NUXT_STUDIO_AUTH_GITHUB_MODERATORS=admin@example.com,editor@example.com # Optional: GitHub entreprise server NUXT_STUDIO_AUTH_GITHUB_INSTANCE_URL=https://gh-ent.com ``` :: ### GitLab GitLab OAuth provides authentication with automatic Git access. Users who authenticate via GitLab can immediately push changes to the repository. ::steps{level="4"} #### Navigate to GitLab Applications Go to your GitLab [User Settings → Applications](https://gitlab.com/-/user_settings/applications){rel=""nofollow""} (or your group/organization settings) and create a **New Application**. #### Configure the GitLab OAuth Application Fill in the required fields: - **Application name**: Your app name - **Redirect URI**: `https://yourdomain.com/__nuxt_studio/auth/gitlab` - **Scopes**: Select `api` (required for publication) :::note For local development, add: `http://localhost:3000/__nuxt_studio/auth/gitlab` ::: #### Copy Your GitLab Credentials After creating the OAuth application, you'll receive: - An **Application ID** (visible immediately) - A **Secret** (visible immediately) #### Set GitLab Environment Variables Add the GitLab OAuth credentials to your deployment platform's environment variables or `.env` file: ```bash [.env] NUXT_STUDIO_AUTH_GITLAB_APPLICATION_ID= NUXT_STUDIO_AUTH_GITLAB_APPLICATION_SECRET= # Optional: Restrict access to specific users # NUXT_STUDIO_AUTH_GITLAB_MODERATORS=admin@example.com,editor@example.com # Optional: Self hosted GitLab server NUXT_STUDIO_AUTH_GITLAB_INSTANCE_URL=https://gh-ent.com ``` :: ### Google Google OAuth is ideal for non-technical users who don't have GitHub or GitLab accounts. ::steps{level="4"} #### Navigate to Google Cloud Console Go to [Google Cloud Console](https://console.cloud.google.com/){rel=""nofollow""} and select or create a project, then navigate to **APIs & Services → Credentials**. #### Create OAuth Application Click **Create Credentials** and **OAuth client ID** and select **Web application** as the application type. Fill in the required fields: - **Name**: Your app name - **Authorized redirect URIs**: `https://yourdomain.com/__nuxt_studio/auth/google` :::note For local development, add: `http://localhost:3000/__nuxt_studio/auth/google` ::: After creating the OAuth client, you'll receive: - A **Client ID** - A **Client Secret** :::warning Save these credentials immediately; you may not be able to view them again. ::: #### Create a Personal Access Token Since Google doesn't provide Git access, you must also configure a [Personal Access Token](https://nuxt.studio/git-providers#creating-a-personal-access-token) for repository operations. #### Set Environment Variables Add the Google OAuth credentials, your personal access token and moderator list: :::tabs ::::tabs-item{icon="i-lucide-github" label="With GitHub Repository"} ```bash [.env] NUXT_STUDIO_AUTH_GOOGLE_CLIENT_ID= NUXT_STUDIO_AUTH_GOOGLE_CLIENT_SECRET= NUXT_STUDIO_GIT_GITHUB_TOKEN= NUXT_STUDIO_AUTH_GOOGLE_MODERATORS=admin@example.com,editor@example.com ``` :::: ::::tabs-item{icon="i-lucide-gitlab" label="With GitLab Repository"} ```bash [.env] NUXT_STUDIO_AUTH_GOOGLE_CLIENT_ID= NUXT_STUDIO_AUTH_GOOGLE_CLIENT_SECRET= NUXT_STUDIO_GIT_GITLAB_TOKEN= NUXT_STUDIO_AUTH_GOOGLE_MODERATORS=admin@example.com,editor@example.com ``` :::: ::: :::warning The `NUXT_STUDIO_AUTH_GOOGLE_MODERATORS` environment variable is **required** for Google OAuth. Only users with email addresses in this list can access Studio. ::: :: ### SSO Server SSO (Single Sign-On) allows you to authenticate users via a centralized authentication server like [Nuxt Studio SSO](https://github.com/nuxt-content/nuxt-studio-sso){rel=""nofollow""}. This is ideal for organizations managing multiple Nuxt Studio sites. **Key benefits:** - **Single login**: Users authenticate once and access all connected Studio sites - **GitHub token pass-through**: When users login with GitHub on the SSO server, their GitHub token is automatically passed to Studio sites - **One GitHub OAuth client**: You can use the same GitHub OAuth client for all Studio sites, reducing the number of OAuth clients you need to manage - **Preview URL support**: The SSO server can handle preview URLs for the Studio sites with glob patterns - **Centralized user management**: Manage OAuth clients and users in one place ::steps{level="4"} #### Deploy the SSO Server Deploy [Nuxt Studio SSO](https://github.com/nuxt-content/nuxt-studio-sso){rel=""nofollow""} to your preferred platform (Vercel, Cloudflare, etc.). #### Register Your Site as an OAuth Client 1. Login to the SSO server dashboard 2. Create a new OAuth client with your site's URL 3. Copy the **Client ID** and **Client Secret** #### Set Environment Variables Add the SSO credentials to your deployment platform's environment variables: ```bash [.env] NUXT_STUDIO_AUTH_SSO_SERVER_URL=https://your-sso-server.com NUXT_STUDIO_AUTH_SSO_CLIENT_ID= NUXT_STUDIO_AUTH_SSO_CLIENT_SECRET= ``` :::note The callback URL is automatically set to `https://yourdomain.com/__nuxt_studio/auth/sso` ::: :: ::tip When users authenticate with GitHub on the SSO server, their GitHub access token is automatically passed through to Studio. This means commits are made with the user's own GitHub identity, and no shared PAT is needed. :: ## Custom Authentication For complete control over authentication, you can implement your own auth logic (password forms, SSO, LDAP, etc.) using Studio's session utilities. ::warning{title="Security Responsibility"} When using custom authentication, **you are fully responsible for securing your authentication flow** . Studio only manages the session after you authenticate the user. :: ### Personal Access Token Required You must configure a Personal Access Token for repository operations based on the Git provider you are using. ```bash [.env] # For GitHub repositories NUXT_STUDIO_GIT_GITHUB_TOKEN= # For GitLab repositories NUXT_STUDIO_GIT_GITLAB_TOKEN= ``` See [Git Providers](https://nuxt.studio/git-providers#creating-a-personal-access-token) for instructions on creating a PAT. ### Implementation Flow 1. **Validate the user** in your login handler using your preferred method (password, SSO, etc.) 2. **Create the session** by calling `setStudioUserSession(event, user)` with a `StudioUserSession` object 3. **Handle logout** by calling `clearStudioUserSession(event)` to clear the session ### Required Session Fields When calling `setStudioUserSession`, you must provide: | Field | Type | Required | Description | | ------------ | -------- | -------- | ------------------------------ | | `name` | `string` | ✅ | Display name for the user | | `email` | `string` | ✅ | User's email address | | `providerId` | `string` | ❌ | Unique identifier for the user | | `avatar` | `string` | ❌ | URL to user's avatar image | ### Example: Password-based Login ```ts [server/api/studio/login.ts] import { eventHandler, readBody, createError } from 'h3' import { setStudioUserSession } from '#imports' export default eventHandler(async (event) => { const { email, password } = await readBody<{ email?: string, password?: string }>(event) // ⚠️️ Implement your own secure validation logic here // This is a simplified example - use proper password hashing and validation const user = await validateCredentials(email, password) if (!user) { throw createError({ statusCode: 401, message: 'Invalid credentials' }) } await setStudioUserSession(event, { providerId: user.id, name: user.name, email: user.email, avatar: user.avatar || '' }) return { ok: true } }) ``` ### Example: Logout Handler ```ts [server/api/studio/logout.ts] import { eventHandler } from 'h3' import { clearStudioUserSession } from '#imports' export default eventHandler(async (event) => { await clearStudioUserSession(event) return { ok: true } }) ``` ### Redirecting After Login After successfully setting the session, redirect users to your app root (`/`). Studio will automatically detect the session and activate for that user. ```ts [server/api/studio/login.ts] // After setStudioUserSession... return sendRedirect(event, '/') ``` ## Advanced Options ### Access Control with Moderators You can restrict access to Studio by defining a whitelist of authorized users through the `NUXT_STUDIO_AUTH_{PROVIDER}_MODERATORS` environment variable. ```bash [.env] # GitHub OAuth moderators NUXT_STUDIO_AUTH_GITHUB_MODERATORS=admin@example.com,editor@example.com # GitLab OAuth moderators NUXT_STUDIO_AUTH_GITLAB_MODERATORS=admin@example.com,editor@example.com # Google OAuth moderators (required) NUXT_STUDIO_AUTH_GOOGLE_MODERATORS=admin@example.com,editor@example.com ``` The moderator list is a comma-separated list of email addresses. Only users with matching email addresses will be granted access. #### Behavior by Provider | Provider | Moderator List | Behavior | | ------------ | -------------- | --------------------------------------------------- | | GitHub OAuth | Optional | If empty, all OAuth-authenticated users have access | | GitLab OAuth | Optional | If empty, all OAuth-authenticated users have access | | Google OAuth | **Required** | Without moderators, no one can access Studio | | Custom Auth | N/A | Implement your own access control logic | ::note For GitHub and GitLab OAuth, repository write access is still controlled by OAuth scopes, preventing unauthorized users from pushing changes even if they can access Studio. :: ### Custom Redirect URL By default, Studio uses your deployment URL for OAuth callbacks. To customize the redirect URL: ```bash [.env] # GitHub OAuth NUXT_STUDIO_AUTH_GITHUB_REDIRECT_URL=https://custom-domain.com/__nuxt_studio/auth/github # GitLab OAuth NUXT_STUDIO_AUTH_GITLAB_REDIRECT_URL=https://custom-domain.com/__nuxt_studio/auth/gitlab # Google OAuth NUXT_STUDIO_AUTH_GOOGLE_REDIRECT_URL=https://custom-domain.com/__nuxt_studio/auth/google ``` ::note This is useful when you need to handle OAuth callbacks through a specific endpoint, such as behind a reverse proxy or with custom domain routing. :: ### Server Hooks Studio provides Nitro hooks that allow you to execute custom logic during authentication events. #### `studio:auth:login` Called when a user successfully logs in to Studio. This hook receives the authenticated user and the current H3 event. **Payload:** | Field | Type | Description | | ------- | ------------ | -------------------------------------------- | | `user` | `StudioUser` | The authenticated user object | | `event` | `H3Event` | The H3 event object from the current request | **Example Usage:** ```ts [server/plugins/studio.ts] export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('studio:auth:login', async ({ user, event }) => { console.log(`User ${user.email} logged in via ${user.provider}`) // ... Track login analytics // ... Notifications // ... Extra session management for custom auth }) }) ``` #### `studio:auth:logout` Called when a user logs out from Studio. This hook receives the user who logged out and the current H3 event. **Payload:** | Field | Type | Description | | ------- | ------------ | -------------------------------------------- | | `user` | `StudioUser` | The user object who is logging out | | `event` | `H3Event` | The H3 event object from the current request | **Example Usage:** ```ts [server/plugins/studio.ts] export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('studio:auth:logout', async ({ user, event }) => { // Log logout events console.log(`User ${user.email} logged out`) // ... Clean ressources // ... Notifications // ... Extra session management for custom auth }) }) ``` # Edit your content Nuxt Studio offers a versatile workspace for both developers and content writers, giving them the freedom to choose between our different editors: - [Notion-like editor](https://nuxt.studio/#notion-like-editor-markdown-files) for `Markdown` files - [Form editor](https://nuxt.studio/#form-editor) for `YAML` and `JSON` files - [Code editor](https://nuxt.studio/#code-editor) for any kind of files (for technical users only) Each editor serves its own purpose. Some users prefer visual editing, while others prefer direct code manipulation. At the end, **Markdown and MDC syntax is the final output** for both editors, with seamless conversion between visual and code modes. ## Editor Mode Switching You can switch between the visual editor and code editor at any time by clicking the **actions dropdown** in the file header (on the right of the breadcrumb): - **Visual Mode** - Notion-like editing experience for Markdown and Form editor for YAML/JSON files. - **Code Mode** (Monaco) - Direct Markdown/MDC syntax editing Your preference is saved and will be used for all files. ## TipTap Visual Editor (`Markdown` files) The TipTap visual editor provides a modern Notion-like editing experience for Markdown content, powered by the popular [TipTap](https://tiptap.dev/){rel=""nofollow""} editor integrated by [Nuxt UI Editor](https://ui.nuxt.com/pro/components/editor){rel=""nofollow""}. :video{controls loop poster="/video-thumbnail.jpg" src="https://res.cloudinary.com/nuxt/video/upload/v1767647099/studio/studio-demo_eiofld.mp4"} ### Key Features - **Rich Text Editing** - Format text with headings, bold, italic, strikethrough, code, links, and more - **MDC Component Support** - Insert and edit custom Vue components directly in the visual editor - **Vue Component Props Editor** - Visually edit component properties through a form-based interface - **Data Binding** - Bind component props to frontmatter variables using the [MDC binding syntax](https://content.nuxt.com/docs/files/markdown#binding-data){rel=""nofollow""} - **Span-style Formatting** - Apply inline text styles using the toolbar button for custom text formatting - **Media Integration** - Browse in media picker to insert images from your library - **Emoji Integration** - Type `:` followed by an emoji name to insert emojis, or use the slash command `/emoji` - **Slash Commands** - Type `/` to access a menu of formatting options and components - **Drag & Drop** - Reorder content blocks by dragging them - **Link Editor** - Floating popover for editing links with external link support - **Toolbar** - Bubble toolbar appears when you select text, providing quick access to formatting options - **Real-time Conversion** - Seamlessly converts between visual content and MDC/Markdown syntax ### Integrate Components One of the TipTap visual editor's standout features is its ability to integrate and edit custom Vue components directly within the editor interface. :video{controls loop muted poster="/video-thumbnail.jpg" src="https://res.cloudinary.com/nuxt/video/upload/v1767981444/studio/videfinalfainalfinalvibecoding_tjuudu.mp4"} Developers can create visually complex components, and editors can use them without technical knowledge. The visual editor handles component integration seamlessly. ::steps{level="4"} #### Create your component ```vue [components/content/HomeFeature.vue] ``` #### Use in Markdown with MDC syntax Components can be integrated using [MDC syntax](https://content.nuxt.com/docs/files/markdown#mdc-syntax){rel=""nofollow""}: ```mdc [content/index.md] ::home-feature --- icon: i-mdi-vuejs --- #title Embedded Vue components #description Edit slots and props inside the visual editor. :: ``` #### Ensure components are listed If your component is not used in a `Markdown` file it won't be available in the components listed with the `/` command. To make sure components are listed with the `/` command, make them globally registered: ```ts export default defineNuxtConfig({ components: [ { path: '~/components/content', //globally register all components in the components/contents folder global: true, } ], }) ``` or with perhaps more control: ```ts export default defineNuxtConfig({ hooks: { 'components:extend': (components) => { const globals = components.filter(c => ['MyCustomComponent'].includes(c.pascalName)) globals.forEach(c => c.global = true) } }, }) ``` :::note Components from external libraries (like Nuxt UI) won't appear in Studio's component list. You can use the same hook to make them available. ::: #### Edit in the Visual Editor 1. Type `/` anywhere while editing 2. Search for your component in the list 3. Insert and edit component slots directly in the editor 4. Click on any component to open the **Props Editor** panel and edit properties visually :: ::tip{to="https://nuxt.studio/setup#editor"} Customize which components appear in the editor, restrict icon libraries, and filter built-in slash menu entries using the `studio.editor` option in your `nuxt.config.ts` . :: ### Debug Mode Enable **debug mode** from the footer menu to see the real-time conversion between: - TipTap JSON format - Comark Tree (the compact array-based AST produced by [comark](https://github.com/comarkdown/comark){rel=""nofollow""}) - Final Markdown output This is useful for understanding how content is transformed and to share troubleshooting. ::prose-note Studio uses **comark** as its Markdown parser. When you type in the visual editor, the content flows through: TipTap JSON → MarkdownDocument → raw Markdown. The MarkdownDocument is also what gets stored in the browser SQLite database and committed to Git as raw markdown via `renderMarkdown` . :: ## Form Editor The form editor is used for editing: - **Frontmatter** in `Markdown` files - **YAML** files - **JSON** files It eliminates the need to interact directly with complex file syntax. Instead, forms are automatically generated based on your Nuxt Content [collection schema](https://content.nuxt.com/docs/collections/define){rel=""nofollow""} definition. ### **Defining your form with** `zod` Schema ::note{to="https://content.nuxt.com/docs/collections/define"} Learn more about schema collection definition in the Nuxt Content documentation. :: Once the `schema` property has been defined in your collection, this will automatically generate the corresponding form on Studio interface. ::code-group :::div{icon="i-lucide-eye" label="Generated Form"} :video{controls loop src="https://res.cloudinary.com/nuxt/video/upload/v1767887481/studio/schemaform_tmd4db.mp4"} ::: ```ts [content.config.ts] export default defineContentConfig({ collections: { authors: defineCollection({ type: 'data', source: 'authors/**/*', schema: z.object({ name: z.string(), avatar: z.object({ src: z.string().editor({ input: 'media' }), alt: z.string(), }), to: z.string(), username: z.string(), role: z.enum(['creator', 'maintainer', 'contributor']), order: z.number().default(0), birthDate: z.string().date(), lastCommitAt: z.string().datetime(), icon: z.string().editor({ input: 'icon', iconLibraries: ['lucide'] }), isOpenSourceLover: z.boolean().default(true), modules: z.array(z.string()), }) }, }) ``` :: ### **Native Inputs Mapping** Primitive Zod types are automatically mapped to appropriate form inputs in: - **String** → Text input - **Date** → Date picker - **Number** → Number input - **Boolean** → Toggle switch - **Enum** → Select dropdown - **Arrays of strings** → List of badge inputs - **Arrays of objects** → Accordion of items with embedded form ### Custom Inputs Mapping Studio goes beyond primitive types. You can customise form fields using the `editor` method, which extends Zod types with metadata to empower editor interface. This allows you to define custom inputs or hide fields. #### Usage ```ts [content.config.ts] // Override the auto-generated label title: property(z.string()).editor({ label: 'Page title' }) // Add helper text below the input slug: property(z.string()).editor({ description: 'Used in the URL, e.g. /blog/my-post' }) // Add an info tooltip next to the label role: z.enum(['admin', 'editor']).editor({ tooltip: 'Controls what the user can edit' }) // Icon picker with specific libraries icon: property(z.string()).editor({ input: 'icon', iconLibraries: ['lucide', 'simple-icons'] }) // Media picker image: property(z.string()).editor({ input: 'media' }) // Textarea description: property(z.string()).editor({ input: 'textarea' }) ``` #### Options ##### `input: 'media' | 'icon' | 'textarea'` You can set the editor input type. Currently `icon`, `media` and `textarea` are available. - `icon` - Opens an icon picker with searchable Iconify libraries - `media` - Opens the media picker to select images from your library - `textarea` - Renders a multi-line text input instead of a single-line text field ##### `label: string` Overrides the auto-generated label derived from the field key. Useful when the key name is technical or abbreviated. ##### `description: string` Shown as helper text below the input. Use it to clarify the field's purpose or expected format. ##### `tooltip: string` Shown as an info icon next to the label. On hover, it displays the provided text. Ideal for brief contextual hints that would clutter the description. ##### `iconLibraries: Array` Specifies which [Iconify](https://icones.js.org/){rel=""nofollow""} libraries to display. Use this option to filter and limit the available icon sets. ##### `hidden: boolean` Removes the field from the Studio editor entirely. ::warning When `hidden` is set, it ensures the field is removed if defined in file raw code (frontmatter or YAML/JSON content). :: #### Hints example ::code-group :::div{icon="i-lucide-eye" label="Form with hints"} ![Form hints: label, description and tooltip](https://nuxt.studio/studio/form-hints.png){.rounded-lg.border.border-muted} ::: ```ts [content.config.ts] import { defineContentConfig, defineCollection, property } from '@nuxt/content' import z from 'zod' export default defineContentConfig({ collections: { authors: defineCollection({ type: 'data', source: 'authors/**/*', schema: z.object({ name: property(z.string()).editor({ description: 'Full display name shown on the author page', tooltip: 'Use your real name or well-known alias', }), avatar: z.object({ src: property(z.string()).editor({ input: 'media', label: 'Avatar image' }), alt: property(z.string()).editor({ label: 'Alt text', tooltip: 'Keep it short: "Jane Doe avatar"' }), }), bio: property(z.string()).editor({ input: 'textarea', label: 'Biography', description: 'Short biography displayed on the author card. Aim for 1–2 sentences.', }), role: z.enum(['creator', 'maintainer', 'contributor']).editor({ tooltip: 'Controls what badge is shown on the author card', }), }), }), }, }) ``` :: ::tip{to="https://nuxt.studio/setup#editor"} Studio inputs are fully extensible. We can create as many input as we want based on our users needs. :: ## Code Editor The Monaco code editor provides full control over your content, allowing you to write raw content directly: MDC syntax for `Markdown` files or `JSON` or `YAML` syntax for others. # Manage and integrate Medias in Nuxt Content Studio CMS ## Browse All medias located in the `/public` directory are available in the **Media** tab of the Studio interface. It's an intuitive interface for non-technical users to manage their `/public` directory or external storage if set. Users can easily browse folders, upload new media at any level, and drag and drop files, making media organization straightforward. ## Editor integration The TipTap visual editor provides seamless media integration: - **Drag and drop** - Simply drag and drop images directly into the editor. An upload modal will open to let you choose the destination folder :badge[Coming Soon] - **Slash commands** - Type `/` and search for `Image` to quickly insert a media. A modal will open to let you choose the media from your library - **Alt text support** - From the media modal, you can set the [alt attribute](https://www.w3schools.com/tags/att_img_alt.asp){rel=""nofollow""} for SEO and accessibility - **Attributes support** - From the media modal, you can set additional attributes like width, height, and custom properties for advanced use cases. ## Supported file types The media library supports the following file types: - **Images**: PNG, JPG/JPEG, SVG, WebP, AVIF, ICO, GIF - **Videos**: MP4, MOV, AVI, MKV, WebM, OGG - **Audio**: MP3, WAV, AAC, M4A ## Storage Comparison | Feature | Default storage | External storage | | ---------------- | ---------------------- | -------------------------------------------------- | | Setup | Zero configuration | Requires `@nuxthub/core` with blob storage enabled | | Storage location | `/public` directory | Cloudflare R2, Vercel Blob, or S3-compatible | | Git commits | Files committed to Git | Only URLs referenced | | Repository size | Grows with media | Stays small | | Best for | Small projects | Large media libraries | ## Default storage (`public` folder) By default, media files are stored in your `/public` directory and committed to Git when you publish. This works great for most projects and requires zero configuration. Studio tracks all changes to the public folder as drafts in the browser until you publish. A service worker intercepts media requests to serve draft versions instantly, so you see your changes in the preview before they are committed. ## External Storage For larger projects or teams with many media files, you can configure **external storage** to upload files directly to cloud storage. Studio uses [NuxtHub Blob Storage](https://hub.nuxt.com/docs/blob){rel=""nofollow""} which supports Vercel Blob, AWS S3 and other S3-compatible providers. ::note Files uploaded to external storage are **not committed to Git** . They are stored permanently in your cloud storage bucket and accessed via their public URL. :: ### Configuration ::steps{level="4"} #### Install the NuxtHub module ```bash [Terminal] npx nuxi module add hub ``` #### Enable blob storage and Studio external media: ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxthub/core', 'nuxt-studio'], hub: { blob: true, }, studio: { media: { external: true, }, }, }) ``` :::warning `@nuxthub/core` must be listed **before** `nuxt-studio` in the `modules` array. ::: #### Set a driver :::note By default, if NuxtHub cannot detect a driver, files are stored locally in the `.data/blob` directory. ::: :::tabs{sync="blob-provider"} ::::tabs-item{icon="i-simple-icons-amazons3" label="S3 compatible storage"} To configure [Amazon S3](https://aws.amazon.com/s3/){rel=""nofollow""} or any other S3-compatible storage: **Install the** `aws4fetch` **package:** :pm-install{name="aws4fetch"} **Set the following environment variables:** ```bash [.env] S3_ACCESS_KEY_ID=your-access-key-id S3_SECRET_ACCESS_KEY=your-secret-access-key S3_BUCKET=your-bucket-name S3_REGION=your-region # (optional) NUXT_PUBLIC_STUDIO_MEDIA_PUBLIC_URL=your-public-url # (optional, mandatory for Cloudflare R2 provider) S3_ENDPOINT=your-endpoint ``` :::: ::::tabs-item{icon="i-simple-icons-vercel" label="Vercel Blob"} To configure [Vercel Blob Storage](https://vercel.com/docs/storage/vercel-blob){rel=""nofollow""}: **Install the** `@vercel/blob` **package:** :pm-install{name="@vercel/blob"} **Then follow these steps according to the environment you are using:** 1. Assign a Vercel Blob Store to your project from the [Vercel dashboard](https://vercel.com/){rel=""nofollow""} -> Project -> Storage **in production**. 2. Use `VERCEL_BLOB_READ_WRITE_TOKEN` in **local development** to authenticate. ```bash [.env] VERCEL_BLOB_READ_WRITE_TOKEN=your-token ``` :::: ::::tabs-item{icon="i-simple-icons-cloudflare" label="Cloudflare R2"} When deploying to Cloudflare, configure [Cloudflare R2](https://developers.cloudflare.com/r2/){rel=""nofollow""} by providing the bucket name. NuxtHub auto-generates the wrangler bindings at build time. ```ts [nuxt.config.ts] export default defineNuxtConfig({ hub: { blob: { driver: 'cloudflare-r2', bucketName: '' } } }) ``` :::::note To use Cloudflare R2 without hosting on Cloudflare Workers, use S3 compatible storage with the appropriate environment variables. ::::: :::: \::: ::: :: ::tip{to="https://hub.nuxt.com/docs/storage/blob"} See the NuxtHub blob documentation for detailed provider setup and environment variable configuration. :: ### Options ::note Studio only manages files stored under the configured prefix in your bucket. Files uploaded through the Studio interface are stored as `/` (default: `studio/` ) and only those files are listed, browsed, or deleted from within Studio. Any other files already in your bucket are left untouched. :: | Option | Type | Default | Description | | -------------- | ---------- | ----------------------------------- | --------------------------------------------------------------------------- | | `external` | `boolean` | `false` | Enable external storage via NuxtHub blob | | `maxFileSize` | `number` | `10485760` (10 MB) | Maximum upload size in bytes, enforced server-side | | `allowedTypes` | `string[]` | `['image/*', 'video/*', 'audio/*']` | Allowed MIME types. Wildcards supported (e.g. `image/*`) | | `prefix` | `string` | `'studio'` | Bucket key prefix for uploaded files. Files are stored as `/` | ## Examples List of examples for different storage providers. ### Vercel Blob ::steps{level="4"} #### Create a Blob store 1. Go to your [Vercel Dashboard](https://vercel.com/dashboard){rel=""nofollow""} and open the **Storage** tab 2. Click **Create Database**, select **Blob**, and give it a name 3. Copy the `BLOB_READ_WRITE_TOKEN` value shown after creation #### Set environment variables ```bash [.env] BLOB_READ_WRITE_TOKEN=vercel_blob_rw_************ ``` #### Link to your project in production From your Vercel project, go to **Storage** and connect the blob store you just created. Vercel will automatically inject the `BLOB_READ_WRITE_TOKEN` environment variable at runtime. #### Enable and configure NuxtHub Install the NuxtHub module and enable blob storage: ```bash [Terminal] npx nuxi module add hub ``` ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxthub/core', 'nuxt-studio'], hub: { blob: true, }, studio: { media: { external: true, }, }, }) ``` :: ### Cloudflare R2 ::steps{level="4"} #### Create an R2 bucket 1. Go to [Cloudflare Dashboard](https://dash.cloudflare.com){rel=""nofollow""} and navigate to **R2 Object Storage** 2. Click **Create bucket** and give it a name (e.g., `my-studio-media`) 3. In your bucket **Settings**, enable **Public access** (via an `r2.dev` subdomain or a custom domain) so uploaded files are publicly accessible #### Create an R2 API token 1. From the [R2 overview](https://dash.cloudflare.com/?to=/\:account/r2/overview){rel=""nofollow""} page, click **Manage R2 API Tokens** 2. **Create API token** and set the permission to **Object Read & Write** — this allows Studio to read, write, and list objects 3. Under **Specify bucket(s)**, select your bucket (e.g., `my-studio-media`) to restrict the token's scope #### Set environment variables Your **Account ID** is visible in the right sidebar of the R2 overview page or in your Cloudflare account URL. ```bash [.env] S3_ACCESS_KEY_ID= S3_SECRET_ACCESS_KEY= S3_ENDPOINT=https://.r2.cloudflarestorage.com S3_BUCKET=my-studio-media NUXT_PUBLIC_STUDIO_MEDIA_PUBLIC_URL=https://pub-.r2.dev # or your custom domain ``` #### Enable and configure NuxtHub Install the NuxtHub module and enable blob storage: ```bash [Terminal] npx nuxi module add hub ``` ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxthub/core', 'nuxt-studio'], hub: { blob: true, }, studio: { media: { external: true, }, }, }) ``` :: # AI-Powered Content Assistance in Nuxt Studio Nuxt Studio integrates AI-powered content assistance using models via Vercel AI Gateway. These features help you write, improve, and transform content faster with intelligent suggestions and transformations. :video{autoplay controls loop src="https://res.cloudinary.com/nuxt/video/upload/v1770661582/studio/studio-ai_bsmqs6.mp4"} ::note{type="info"} AI features are optional and require a Vercel AI Gateway API key. Vercel provides $5 in free credits, which is typically sufficient for most editing scenarios over several days. :: ## Configuration ### Vercel AI Gateway Setup Vercel AI Gateway provides a unified API for accessing AI models with built-in caching, rate limiting, and analytics. ::steps{level="4"} #### Create a Vercel Account Sign up at [vercel.com](https://vercel.com){rel=""nofollow""} if you don't have an account. #### Access AI Gateway Navigate to your [Vercel AI Gateway Dashboard](https://vercel.com/ai-gateway){rel=""nofollow""} in your account settings. #### Create an API Key Generate a new API key from the AI Gateway dashboard. :: ### Set Environment Variable To enable AI-powered editing in Studio, simply set the `NUXT_STUDIO_AI_API_KEY` environment variable. ```bash [.env] NUXT_STUDIO_AI_API_KEY=your_vercel_ai_gateway_api_key ``` ### Contextualization You can then configure AI behavior in your `nuxt.config.ts` file to provide contextual guidance and ensure the AI output aligns with your preferred writing style. ```ts [nuxt.config.ts] export default defineNuxtConfig({ studio: { ai: { context: { title: 'My Awesome Blog', description: 'A technical blog about web development', style: 'Technical and detailed, with practical examples', tone: 'Professional yet approachable' } } } }) ``` ## Features ### AI Completion AI Completion provides intelligent text suggestions as you type in the TipTap visual editor. #### How It Works - **Auto-triggers** after 500ms when you stop typing - **Manual trigger**: Press `Cmd/Ctrl+J` to request a suggestion anytime - **Accept**: Press `Tab` to insert the suggestion - **Dismiss**: Press `Escape` or continue typing to ignore - Powered by **Claude Haiku 4.5** for fast responses (\~300-500ms) - **Toggle On/Off:** Click the sparkles ✨ button in the footer toolbar to enable/disable AI completion ### AI Transform Transform selected text with AI-powered improvements, fixes, translations, and simplifications. #### Available Modes - **Fix**: Correct grammar and spelling errors - **Improve**: Enhance clarity, engagement, and readability - **Simplify**: Use simpler words and shorter sentences - **Translate**: Translate to another language (language input selection) #### How it works - **Trigger** the AI Transform tool from the content editor toolbar - **Select** a mode from the dropdown menu - **Review** the proposed changes in the preview pane before applying them. - **Accept or decline** the AI proposition - Powered by **Claude Sonnet 4.5** for high-quality results ### Contextualization For both completion and transform features, the AI system is contextualized and enriched to deliver more accurate and relevant suggestions: - **Context-aware :** The system considers surrounding content and cursor position to provide intelligent recommendations. - **MDC Component**: The AI provides context-aware assistance tailored to specific component types, including component names, slot configurations, and structural patterns. ## Experimental Collection Context ::note This feature is under development and may change in future releases. :: Once enable , you can access the AI Tab by clicking the AI icon in the top navigation bar of Nuxt Studio. The AI Tab allows you to generate and manage AI writing style guides per collection, giving AI deeper context about your content structure. ### What It Does - **Analyzes Collections**: Examines your content collections to understand structure and style - **Generates Style Guides**: Creates comprehensive writing guidelines for each collection - **Contextualizes AI**: Uses collection-specific context for better AI suggestions ### How to Enable To enable AI-powered style guide generation, you need to configure your Nuxt project with the experimental feature flag and then activate the AI features in Studio. ```js [nuxt.config.ts] export default defineNuxtConfig({ studio: { ai: { experimental: { collectionContext: true // Enable AI Tab } } } }) ``` ### How It Works 1. **Access**: Click the AI tab button in the Studio interface 2. **Select Collection**: Choose a collection to analyze 3. **Generate Context**: Click "Analyze Collection" to create a style guide 4. **Edit Context**: Review and customize the generated guidelines 5. **AI Uses Context**: Future AI operations will reference these guidelines ### Context Files Context files are stored in the `.studio` folder as `${collectionName}.md` files. These files are automatically loaded during AI operations to provide collection-specific guidance. ## Cost Estimation With Vercel AI Gateway's **$5 free credits**, you can expect: - **\~11,000 AI completions**: Auto-suggestions as you type - **\~120 AI transforms**: Grammar fixes, improvements, translations - **\~2 million words**: Total AI-generated content - **Several weeks**: Typical usage (varies from occasional to regular content creation) After free credits, pricing follows Vercel AI Gateway's standard rates based on model usage. # Advanced This page explains how Nuxt Studio keeps your content synchronized between the browser (your production site) and your Git prodiver repository. ::note This section is informational. Synchronization happens automatically after setup and requires no manual action. :: ## Architecture Overview The self-hosted Nuxt Studio module uses a three-tier system for storage: ### Content Parsing (comark) Studio uses **[comark](https://github.com/comarkdown/comark){rel=""nofollow""}** as its Markdown parser. At build time and in development, markdown files are parsed by comark into a `MarkdownDocument` — a compact array-based representation where each node is `[tag, attributes, ...children]`. This tree is stored in the SQLite database and used as the interchange format between all Studio editors: - **Visual editor** (TipTap) ↔ MarkdownDocument ↔ stored in SQLite - **Code editor** ↔ raw Markdown ↔ `renderMarkdown(tree)` from comark regenerates it - **Publishing** ↔ comark renders the MarkdownDocument back to raw Markdown before committing to Git ### Production Database (SQLite WASM) When the application loads in the browser, Nuxt Content v3 downloads a SQLite database dump from the server and initializes a local WASM database. This local database contains all content from the `content/` directory of your deployed branch. ::warning This browser-side database stays in sync with your Git repository as long as your last deployment was built successfully and your CI/CD pipeline completed without errors. :: ### Draft Storage (IndexedDB) Studio maintains a separate **draft layer** using [unstorage](https://unstorage.unjs.io/){rel=""nofollow""}, backed by IndexedDB. When you edit content, your changes are stored as *drafts* in this layer. Each time the Studio app loads, these drafts are merged with the SQLite database to render a *drafted* version of your production site. ::note Drafts are stored only in your local browser. They are not shared between editors or devices. :: ### Git Repository When you publish, Studio commits your draft changes directly to your Git repository (GitHub or GitLab) through the provider's API. Your CI/CD pipeline then rebuilds and redeploys your site automatically. ::warning After deployment, you need to wait for your build to complete successfully, then refresh your site to update your browser database with your latest content. :: ::tip The Studio module will automatically notify you when a new deployment is detected. :: ## How Synchronization Works ### Initial Load ::steps{level="4"} #### Database Initialization Nuxt Content downloads the SQLite database dump generated during the build process. This file contains all parsed content from your `content/` directory. #### Draft Recovery Studio checks IndexedDB for any existing drafts from previous sessions and loads them into the SQLite database. #### Preview Studio refreshes the site preview so you can view your latest drafts and edits directly on your production website. :: ### Editing Content When you modify content in Studio: ::steps{level="4"} #### Draft Modification Changes are saved immediately in IndexedDB as draft items with a status of `created`, `modified`, or `deleted`. #### Database Update The local SQLite database is updated to include your draft content, allowing instant visual preview. #### Conflict Detection Studio compares your draft content against the latest version in your Git repository to detect possible conflicts. Conflicts can occur when: - Someone pushes a commit that modifies the same file and its version is currently building. - A deployment fails or hasn't completed, leaving the production database out of date and out of sync with your Git repository. :: ### Publishing Changes When you publish your edits: ::steps{level="4"} #### Draft Collection Studio gathers all draft items that contain changes. #### Git Commit Using your Git provider's API, Studio creates a new commit with all updated files. #### Deployment Trigger Your CI/CD platform detects the commit and automatically rebuilds and redeploys your website. #### Deployment Wait After publication, Studio clears the local drafts and waits for the deployment to complete. During this time, a loading state is shown while the production SQLite database catches up with your latest commit. :::warning Until your commit is deployed, Studio remains in a pending state where the production database is not yet up to date. ::: :: # Edit your Nuxt Content website in production ::u-page-hero --- orientation: horizontal --- :::browser-frame :video{controls loop poster="/video-thumbnail.jpg" src="https://res.cloudinary.com/nuxt/video/upload/v1767647099/studio/studio-demo_eiofld.mp4"} ::: #headline :::u-button --- class: mb-3 rounded-full size: sm target: _blank to: https://github.com/nuxt-content/studio trailing-icon: i-lucide-arrow-right variant: outline --- Open Source & Self-hosted ::: #title Edit your [Nuxt]{.text-primary} website in production. #description Self-hosted CMS for Nuxt Content websites. Edit content visually, manage media, and publish changes directly to Git from your production site. #links :::u-button --- label: Get Started size: lg to: https://nuxt.studio/introduction trailingIcon: i-lucide-arrow-right --- ::: :u-input-copy{value="npx nuxi module add nuxt-studio"} :: ::u-container{.pb-12.xl:pb-24} :::u-page-grid ::::u-page-feature --- icon: i-lucide-pen-tool --- #title{unwrap="p"} Visual Editor #description{unwrap="p"} Notion-like editing with MDC component support. Insert Vue components and drag-and-drop blocks. :::: ::::u-page-feature --- icon: i-lucide-form-input --- #title{unwrap="p"} Schema-based Forms #description{unwrap="p"} Auto-generated forms for Frontmatter and YAML/JSON files based on your collection schema. :::: ::::u-page-feature --- icon: i-lucide-image --- #title{unwrap="p"} Media Library #description{unwrap="p"} Centralized media management. Browse folders, upload files, and insert images directly. :::: ::::u-page-feature --- icon: i-lucide-git-branch --- #title{unwrap="p"} Git Integration #description{unwrap="p"} Commit changes directly to GitHub or GitLab. Your CI/CD pipeline handles the rest. :::: ::::u-page-feature --- icon: i-lucide-shield-check --- #title{unwrap="p"} Flexible Auth #description{unwrap="p"} Secure access with GitHub, GitLab, or Google OAuth. Or implement your own auth flow. :::: ::::u-page-feature --- icon: i-lucide-eye --- #title{unwrap="p"} Real-time Preview #description{unwrap="p"} See changes instantly on your production website. Drafts are stored locally until published. :::: ::::u-page-feature --- icon: i-lucide-languages --- #title{unwrap="p"} Multi languages #description{unwrap="p"} Full i18n support for the Studio interface. Available in 25+ languages. :::: ::::u-page-feature --- icon: i-lucide-server --- #title{unwrap="p"} Self-hosted #description{unwrap="p"} Deploy on your own infrastructure with no external dependencies. Free forever under MIT. :::: ::::u-page-feature --- icon: i-lucide-file-code --- #title{unwrap="p"} Code Editor #description{unwrap="p"} Monaco editor for Markdown, MDC, YAML, and JSON files. Switch between visual and code modes. :::: ::: :: ::u-page-section #title Everything you need for content editing #description Edit **Markdown** with **Vue** components, structure data using **YAML** and **JSON** forms, manage media assets and publish directly to **Git**. All from your live production website. :: ::u-page-section --- reverse: true orientation: horizontal --- :::browser-frame ![Visual Markdown Editor](https://nuxt.studio/studio/visual-markdown-editor.webp){.rounded-none height="900" width="1440"} ::: #title Notion-like [Visual Editor]{.text-primary} #description A powerful editor built on TipTap that enables natural content creation while automatically generating complete MDC syntax behind the scenes. Integrate interactive components directly within your content. #features :::u-page-feature --- icon: i-lucide-puzzle --- #title{unwrap="p"} Insert Vue components with props and slots ::: :::u-page-feature --- icon: i-lucide-move --- #title{unwrap="p"} Drag and drop content blocks ::: :::u-page-feature --- icon: i-lucide-eye --- #title{unwrap="p"} Real-time preview on your production site ::: #links :::u-button --- color: neutral icon: i-simple-icons-github label: Learn more about the Visual Editor size: lg target: _blank to: https://nuxt.studio/content trailingIcon: i-lucide-arrow-right variant: subtle --- ::: :: ::u-page-section --- orientation: horizontal --- :::browser-frame ![Schema-based Forms](https://nuxt.studio/studio/json-yml-forms.webp){.rounded-none height="900" width="1440"} ::: #title [Schema-based]{.text-primary} Forms #description Forms are automatically generated from your [Nuxt Content](https://content.nuxt.com){rel=""nofollow""} collection schema. Edit frontmatter, YAML, and JSON files with a beautiful form interface. #features :::u-page-feature --- icon: i-lucide-layout-grid --- #title{unwrap="p"} Auto-generated from collection schema ::: :::u-page-feature --- icon: i-lucide-file-json --- #title{unwrap="p"} YAML and JSON support ::: :::u-page-feature --- icon: i-lucide-list --- #title{unwrap="p"} Frontmatter edition ::: #links :::u-button --- color: neutral label: Learn more about Forms to: https://nuxt.studio/content#form-editor trailingIcon: i-lucide-arrow-right variant: subtle --- ::: :: ::u-page-section --- reverse: true orientation: horizontal --- :::browser-frame ![GitHub Sync](https://nuxt.studio/studio/github-sync.webp){.rounded-none height="900" width="1440"} ::: #title Commit to [Git]{.text-primary} directly #description Publish changes directly to GitHub or GitLab from your production site. Your CI/CD pipeline automatically rebuilds and deploys the updated content. #features :::u-page-feature --- icon: i-simple-icons-github --- #title{unwrap="p"} GitHub and GitLab support ::: :::u-page-feature --- icon: i-lucide-shield-check --- #title{unwrap="p"} Flexible OAuth authentication ::: :::u-page-feature --- icon: i-lucide-workflow --- #title{unwrap="p"} Triggers your CI/CD pipeline ::: #links :::u-button --- color: neutral label: Configure Git providers to: https://nuxt.studio/git-providers trailingIcon: i-lucide-arrow-right variant: subtle --- ::: :: ::u-page-section --- orientation: horizontal --- :::browser-frame :video{controls loop muted src="https://res.cloudinary.com/nuxt/video/upload/v1770661582/studio/studio-ai_bsmqs6.mp4"} ::: #title [AI Powered]{.text-primary} content generation #description Leverage [Vercel AI Gateway](https://vercel.com/ai-gateway){rel=""nofollow""} to automatically generate and refine your content. The system intelligently adapts based on your project context, cursor position, active components, and overall project architecture. #features :::u-page-feature --- icon: i-lucide-sparkles --- #title{unwrap="p"} Built-in features (grammar check, improvements, translation...) ::: :::u-page-feature --- icon: i-lucide-brain --- #title{unwrap="p"} Contextualized autocompletion ::: :::u-page-feature --- icon: i-lucide-plug --- #title{unwrap="p"} One environment variable setup ::: #links :::u-button --- color: neutral label: Learn more about AI integration to: https://nuxt.studio/ai trailingIcon: i-lucide-arrow-right variant: subtle --- ::: :: ::div{.relative.min-h-[400px]} :::div{.hidden.md:block} :cta-background ::: :::u-page-section --- class: relative z-10 --- #title Start editing your Nuxt website today. #links :u-button{label="Get Started" to="https://nuxt.studio/introduction" trailing-icon="i-lucide-arrow-right"} ::::u-button --- color: neutral icon: i-simple-icons-github target: _blank to: https://github.com/nuxt-content/studio variant: outline --- Star on GitHub :::: ::: ::