How to securely handle secrets for triggering builds from hosted Sanity Studio?

4 replies
Last updated: May 13, 2021
I am looking for some advice on how to handle secrets in a hosted sanity studio. I want to be able to trigger a GitLab pipeline in some way, currently investigating Document Actions, so that we can re-build a static site that has it's content hosted in Sanity. I was considering using environment variables to store the GitLab trigger token, but the documentation says not to put secrets in there. What is the recommended approach for this sort of thing? I am starting to look at plugins to see if they are a better fit for what we need.
AI Update

You're right to be cautious about secrets in environment variables! The documentation correctly warns that any environment variables prefixed with SANITY_STUDIO_ are not secret — they get bundled into your frontend code and are visible to anyone who inspects your deployed Studio.

For your use case of triggering a GitLab pipeline when content changes, here's the recommended modern approach:

Sanity Functions are the best solution for this. They're serverless functions that run on Sanity's infrastructure and can securely store secrets. Here's why they're perfect for your needs:

  1. Secure secret management: Functions support environment variables that are truly secret and never exposed to the frontend
  2. Event-driven: They automatically trigger on document changes (publish, create, update, delete)
  3. No external hosting: Everything runs on Sanity's infrastructure
  4. Direct integration: No need to expose tokens in your Studio code

You'd set it up like this:

  1. Initialize a Blueprint with sanity functions init
  2. Create a function that triggers on document publish events
  3. Store your GitLab trigger token as a function environment variable (these are secure)
  4. Make an HTTP request to GitLab's API to trigger your pipeline

The function would look something like:

export default async function(event) {
  const gitlabToken = process.env.GITLAB_TRIGGER_TOKEN; // Secure!
  
  // Call GitLab API to trigger pipeline
  await fetch('https://gitlab.com/api/v4/projects/YOUR_PROJECT/trigger/pipeline', {
    method: 'POST',
    headers: { 'PRIVATE-TOKEN': gitlabToken },
    body: JSON.stringify({ ref: 'main' })
  });
}

Alternative: Webhooks

If you already have external infrastructure or prefer webhooks, you can use Sanity's GROQ-powered webhooks. The webhook secret would be stored on Sanity's side (not in your Studio), and you'd set up an endpoint on your server to receive the webhook and trigger GitLab. This keeps secrets out of your frontend, but requires you to host and maintain the webhook receiver.

Why Not Document Actions or Plugins?

Document Actions run in the Studio (browser), so any API tokens would be exposed in the frontend code — the same problem as SANITY_STUDIO_ environment variables. Plugins have the same limitation since they're part of the Studio bundle.

The key principle: anything that runs in the browser cannot securely hold secrets. You need server-side code, which is exactly what Functions provide without requiring your own infrastructure.

Functions are the modern, recommended approach for this type of automation, and they solve your security concern perfectly while being simpler to set up than webhooks + external hosting.

Show original thread
4 replies
Thank you for your reply. I initially looked at adding a webhook, but the GitLab pipeline trigger needs to have a secure token included in the POST to the API endpoint, which I believe is not possible with the webhook approach.I believe that the intention is to upgrade to an Advanced plan once we have proven that we can work with Sanity in the way that we need, so I guess the Custom Access Controls will be out of scope for our potential solution.
I will keep looking into the plugin approach, as things like the sanity => netlify plugin appear to store secrets in their configuration, so I just need to understand how secure that is.
Thank you for your reply. I initially looked at adding a webhook, but the GitLab pipeline trigger needs to have a secure token included in the POST to the API endpoint, which I believe is not possible with the webhook approach.I believe that the intention is to upgrade to an Advanced plan once we have proven that we can work with Sanity in the way that we need, so I guess the Custom Access Controls will be out of scope for our potential solution.
I will keep looking into the plugin approach, as things like the sanity => netlify plugin appear to store secrets in their configuration, so I just need to understand how secure that is.
As a follow up question, do you have any ideas if the outbound calls made by the webhooks come from specific IPs or IP ranges? Just trying to work out how we might secure a lambda function so that it isn't completely public, but allow a webhook to invoke it
Unfortunately due to the cloud architecture, it's all coming from random IPs, so there's probably not a consistent way of checking against that.
Thinking a bit more about the plugin i posted above. You could potentially create a second dataset for your project to house your secrets and make that entire dataset private. At that point, you'd need to make an authenticated API call to the private dataset to get and transmit your secret to GitLab during the script you run for the Custom Document Action you mentioned

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?