Modulify

Secrets

Store API keys and configuration as environment variables your site reads at runtime.

On this page

A secret is one named value your site reads from its environment, such as a payment provider key, a mail service token or a feature flag. You store it in the Secrets tab and read it in code as process.env.KEY_NAME.

Secrets exist so credentials never live in your source files. A key pasted into a component ends up in the GitHub repository behind your project. A key stored here is encrypted at rest and handed to the machine that runs your site instead.

Open the Secrets tab

Secrets lives behind More in the editor tab strip. Click More, then Secrets in the list on the left. Its address is /dashboard/projects/<your-project>/secrets, so the link is shareable and survives a reload.

Secrets is the first entry in that list, so the first time you ever open More you land on it. After that More reopens whichever of the nine tabs you used last. Tabs and visibility covers the rest of the strip.

Before you add anything the panel reads No secrets with Secrets you add are encrypted at rest.

Add a secret

Type the name

The KEY field takes the variable name. Use letters, digits and underscores, starting with a letter or an underscore, the way environment variables are normally written: STRIPE_SECRET_KEY, RESEND_API_KEY, NEXT_PUBLIC_SITE_URL. Anything else in the name is replaced with underscores when it saves.

Type the value

The value field takes the value itself. A value is stored up to 10,000 characters and a name up to 255.

Choose Secret or Public

The lock icon next to the value opens a two entry menu: Secret and Public. Secret is the default.

Add it

Press the plus button, or press Enter in either field. You get a Secret saved toast and the list scrolls to the new row and highlights it briefly.

The plus button stays disabled while the name is empty, while the name duplicates one you already have, and while the name is one the platform reserves.

  • A duplicate name outlines the field in red and reads A secret with this key already exists.
  • A reserved name reads This key is reserved by the system and cannot be used.

Secret or Public

The choice controls how the value is stored, not who can see it in a browser.

Choice Icon How it is stored
Secret Lock Encrypted at rest with AES-256-GCM, decrypted only when you reveal it or when it is handed to your site
Public Globe Stored as plain text

Both kinds are injected into your site's environment the same way and both are readable from your server code as process.env. Neither is automatically sent to the browser. Whether a value reaches the browser is decided by the name: a Next.js project exposes a variable to client code only when its name starts with NEXT_PUBLIC_.

Never name a real credential with the NEXT_PUBLIC_ prefix. That prefix is what puts the value into the JavaScript bundle your visitors download, whichever storage option you picked.

What happens when you save

Saving one secret triggers three things.

Your preview updates straight away. The whole set of variables is pushed to the machine running your preview, its environment is replaced with the new set, and the dev server restarts if anything actually changed. The API Logs stream records a line like Runtime environment synced (12 active). A variable you deleted is removed from the environment in the same push.

Your published site does not change. The live machines were given their environment when you last published, so they keep serving the old value. Publish again for a new or changed secret to take effect. The values are read once per publish and go to both the build and the running server, so a value read at build time is baked into the output of that build.

Everyone else in the project sees it. The list refreshes for every collaborator who has the tab open.

Because values are read at build time as well as runtime, a tracking id or a public URL only starts working on the next publish. Publish a site covers that step.

Read a value back

Values are not write-only. Every row shows eight dots until you click the eye icon on it, which fetches the value and shows it.

For a Public row the value is already in the list, so the eye reveals it with no round trip. For a Secret row the eye decrypts it on the server first, and the icon spins while it does. Clicking the dots themselves opens the value for editing, which reveals it too.

Each row also has two copy icons, one on the name and one on the value. Copying a hidden value reveals it first.

Every accepted member of the workspace can open this tab and reveal every value in the project. Workspace access is implicit in every role and there is no separate secrets permission to withhold, so if someone should not see your production keys, they should not be a member of the workspace that owns the project. See Members and roles.

Edit and rename

Both fields on a row edit in place. Click the name or the value, change it, then press Enter or click away to save. Escape cancels and puts the old text back.

Editing a value keeps the row's existing Secret or Public choice. There is no way to flip an existing row between the two, so to change that, delete the row and add it again.

Renaming an encrypted secret reveals its value first, so the value carries over to the new name. If the reveal is refused or cancelled, the rename is abandoned and the key is left as it was.

Delete

The trash icon on a row deletes it. You are asked to confirm with Delete this secret? and the dialog warns that this permanently deletes the secret and removes it from your site, and that it cannot be undone.

Deleting removes the variable from the preview environment on the next sync. Your published site keeps it until you publish again, so a site that depends on the value keeps working live and breaks on the next build.

To delete several at once, tick the checkbox on each row and open the three dots menu in the list header, which now offers Delete Selected. The checkbox in the header selects and clears everything selectable.

Copy and export

The three dots menu in the list header holds two more entries:

  • Copy as .env puts every row on your clipboard as KEY=value lines, and you get a Copied toast.
  • Download .env file saves the same lines as a file named secrets-2026-08-25.env, dated the day you export.

With rows ticked, both act on the selection instead of everything. Either action reveals any hidden values it needs first.

Managed keys are left out of both.

An exported .env file is plain text with every one of your production credentials in it. Do not put it in the project folder, do not attach it to chat, and delete it when you are done with it.

Import a .env file

Drag a .env file anywhere onto the panel. An overlay reads Drop .env file to import and the file is parsed and saved on drop.

The parser reads KEY=value lines, ignores blank lines and # comments, accepts a leading export , and strips one layer of matching single or double quotes from the value. It stops at 200 entries.

  • A file over 1 MB is refused with Drop a .env file under 1 MB!
  • A file with a line that has no =, or a name that is not a valid variable name, imports nothing and reads No secrets found in this file. Expected KEY=value lines!
  • Reserved names in the file are skipped, and a toast names how many were skipped.

An import decides Secret or Public by name on its own: keys starting with NEXT_PUBLIC_ are stored as plain text, everything else is encrypted. Existing keys are overwritten with the imported value.

Reserved names

Some names belong to the platform and are refused.

DATABASE_URL
CDN_URL
CDN_PRIVATE_KEY
GITHUB_TOKEN
GITHUB_REPO_URL
WS_TOKEN
PROJECT_ID
CLOUDFLARE_CDN_DOMAIN
PREVIEW_BYPASS_TOKEN
MODULIFY_APP_URL
MODULIFY_API_URL

Every name beginning with __MODULIFY_ is reserved as well. Names beginning with ANTHROPIC_ or CLAUDE_CODE_ are also reserved: those save without an error but are stripped before the set is handed to your site, so a value stored under one never reaches your code.

Managed secrets

Some reserved names appear in your list anyway, with a Managed badge. Hovering it reads "This secret is managed by Modulify and cannot be edited or removed."

DATABASE_URL is the one you will meet first. It appears when your project gets a database and holds the connection string. CDN_URL and CDN_PRIVATE_KEY appear for file storage the same way. You can reveal and copy a managed value, but you cannot rename it, change it or delete it, and neither can the AI. It lives and dies with the thing that created it.

Managed rows sort to the bottom of the list. Public rows sort first, then encrypted ones, alphabetically within each band. The list header counts them, reading 12 secrets, or 3 selected once you tick something.

What the AI can do

The AI working in chat can list your key names, create keys, set values and delete keys. It cannot read an existing value, and there is no tool anywhere that reveals one to it.

That shapes how it works. Ask for a Stripe checkout and it creates STRIPE_SECRET_KEY for you with the literal placeholder value REPLACE_ME, writes the code against process.env.STRIPE_SECRET_KEY, and tells you to open the Secrets tab and replace the placeholder. It will not ask you to paste the real key into chat, and it will not create a .env file in your project. Writing prompts covers that convention.

The same actions are available over MCP as list_secrets, set_secret, set_secrets for a whole .env at once, and delete_secret. Reading a value back needs get_secret or get_all_secrets, which sit behind the separate credentials:reveal scope because they hand the client the real value in plain text. See MCP tools.

Secrets and the CMS are different things

Both hold data your site reads, and it is worth being clear about which one a value belongs in.

Secrets The CMS
Holds One value per name, for the whole site Rows in tables, as many as you like
For Credentials, endpoints, configuration Content: posts, products, team members
Reached from code as process.env.KEY_NAME A database query
Changes take effect In the preview at once, live on the next publish Live at once
Who edits it Workspace members, in this tab Workspace members, in the CMS tab

The rule of thumb: if the site would break without it and you would not show it to a visitor, it is a secret. If it is something the site displays and you expect to add more of, it belongs in the CMS.

Limits

A project holds up to 200 secrets. At the cap the plus button is disabled and its tooltip reads Limit reached (200). Contact support for more. An import that would take you past the cap is refused before anything is written.

Names are capped at 255 characters and values at 10,000.

Secrets when a project is copied

Cloning or duplicating a project copies your key names across but not your values, unless the owner turned on Include environment variables in the Share panel. Without it every copied key arrives holding the literal text VALUE_HERE for the new owner to fill in. Managed and reserved keys are never copied. Sharing and cloning covers those toggles.

FAQ

Do I need to publish for a secret to take effect?

For your live site, yes. The preview picks up a change within seconds, but the published machines were handed their environment at publish time and keep the old value until you publish again.

Can I see a value after saving it?

Yes. Click the eye icon on the row. Encrypted values are decrypted on the server for that request.

Can the AI read my API keys?

No. It can list the names, create keys and delete them, but no tool returns a stored value.

Why is DATABASE_URL locked?

It is created and owned by your site database, so its value has to match the database that actually exists. Delete the database and it goes with it.

Should I put a .env file in my project?

No. A .env in the project directory is committed to the GitHub repository behind your site, which leaks every value in it. That is the reason this tab exists.

Next