Blog
How to organize prompts efficiently
Organize prompts by the job they do rather than the model they target: give each a predictable name, extract every changing value into a named variable, keep one prompt per task instead of branching inside a template, and separate what is being tested from what is serving production traffic.
A prompt library goes bad quietly. It starts as six prompts everyone remembers. Then someone adds summarize-v2-final, someone else copies a prompt to tweak it for a different model, and a year later there are forty prompts and nobody is confident which ones production actually calls.
Nothing dramatic happened. There was just never a structure.
Name prompts after the job, not the model
The most common naming mistake is encoding implementation details into the name: gpt4-summarizer, claude-ticket-parser-v3.
Model names age badly. When you migrate providers, either every name lies or you rename everything and break every caller. Version suffixes are worse, that is what versioning is for, and a name ending in -v3 guarantees a -v3-final eventually.
Name for the job being done:
| Avoid | Prefer |
|---|---|
gpt4-summarizer | support-ticket-summary |
claude-parser-v3 | invoice-field-extraction |
prompt-new-2 | onboarding-welcome-email |
A good test: if someone reads the name in an application log with no other context, do they know what it does?
Extract every changing value into a variable
The second failure mode is prompts that differ only in a substituted value. Someone needs the summary prompt for a different tone, copies it, changes two words, and now there are two prompts that will drift apart.
If a value changes per call, it is a variable, not a new prompt.
Summarize the following support ticket in {{sentence_count}} sentences.
Tone: {{tone}}.
Ticket:
{{ticket_body}}Define variables alongside the template, each with a name, a type, and a default where one makes sense. Variables discovered only at call time are undocumented API surface: when a caller forgets one, the template renders with a blank where meaning should be, and the model produces something confidently wrong rather than failing.
One prompt per task, fork when wording truly diverges
Resist branching inside a template. A prompt with {{#if model == "claude"}} inside it is two prompts wearing a trench coat, and it will be impossible to evaluate either half.
The rule of thumb:
- Same task, different values → one prompt, more variables
- Same task, genuinely different instructions per model → fork into separate prompts
- Different task → always a separate prompt
Forking is not a failure. It is the correct move when two paths have really diverged, and it is much easier to reason about than conditional logic inside a template.
Separate what is being tested from what is serving
This is the structural decision that matters most, and the one teams skip.
Authoring, testing, and serving production traffic are three different activities, and they need to be visibly separate. If editing a prompt immediately changes what customers see, every experiment is a production change.
The pattern that works:
- Edit the draft freely. It affects nobody
- Publish when the change is worth keeping. This creates an immutable version
- Point a non-production environment at the new version and exercise it
- Move production only when you mean it
The point is not process for its own sake. It is that "I am trying something" and "customers are now seeing this" should never be the same keystroke.
Decide who can change what
Once more than one person touches prompts, access matters, and it is usually the thing that gets bolted on too late.
Two questions worth answering early:
Who can move production? Publishing a version and promoting it are different privileges. Plenty of teams want writers publishing freely while a smaller group controls what production resolves.
What can each integration do? A service that only renders prompts should hold a read-scoped key. If your summarization worker's credentials can also delete prompts, that is a blast radius with no upside.
A structure that holds up
Putting it together, a library that stays navigable past the first dozen prompts tends to look like this:
- Prompts named for their job, stable across model migrations
- Every per-call value a declared variable with a type
- One prompt per task; forks where instructions genuinely diverge
- Drafts, published versions, and environment pointers kept distinct
- Scoped keys, with production promotion held more tightly than authoring
None of this requires a particular tool. It does require deciding on the structure before the library grows past the point where anyone remembers what is in it.
Related reading
What is prompt versioning? covers the draft/version/environment model in more depth. Prompt API best practices covers the calling side, pinning versions, handling missing variables, and scoping keys.
PromptCache organizes prompts this way by default: named prompts with typed variables, published versions, and environment slots. See how it works or browse the public gallery.
Frequently asked questions
- How many prompts is too many for one workspace?
- Volume matters less than whether a prompt can be found by name. Problems begin when two prompts do nearly the same thing and nobody can say which one production uses.
- Should each model have its own prompt?
- Only where wording genuinely diverges. Prefer one prompt per task with variables for the parts that change; fork to a separate prompt when a model needs materially different instructions.
- Where should prompt variables be defined?
- Alongside the template, with a name, type, and default. Variables discovered only at call time become undocumented API surface that breaks silently.