The system prompt (called the developer message in OpenAI's newer APIs) holds standing instructions from whoever builds the application: the model's role, rules, tone and output format. User messages hold what the end user asks on each turn, and when the two conflict, the system or developer instructions take priority.
OpenAI's prompt engineering guide offers a useful analogy: developer messages are like a function definition, and user messages are like the arguments passed to it.
The four roles in chat APIs
| Role | Who writes it | Typical content |
|---|---|---|
system / developer | The application developer | Role, rules, constraints, format, background facts that apply to every turn |
user | The end user (or your code acting for them) | The question or task for this turn, plus any input data |
assistant | The model | Earlier replies, sent back so the model sees the conversation |
tool results | Your code, after running a tool the model asked for | Function output, search results, file contents |
OpenAI's text generation guide defines the first three directly: "developer messages are instructions provided by the application developer, prioritized ahead of user messages", "user messages are instructions provided by an end user, prioritized behind developer messages", and messages generated by the model have the assistant role. Its API reference accepts user, assistant, system and developer as input roles and states that developer or system instructions take precedence over user instructions.
Anthropic's docs describe the same split from the operator's point of view: a user message is treated as coming from the end user, while a system message is treated as coming from you, the application operator. "When the two conflict, system instructions take precedence."
What goes in the system prompt
- The role and audience. Anthropic's prompting guide notes that setting a role in the system prompt focuses behaviour and tone, and that "even a single sentence makes a difference", for example
You are a helpful coding assistant specializing in Python. - Rules that hold for every turn. Scope ("Only answer questions about our product"), safety limits, what to do when information is missing.
- Output format. "Reply in plain text under 100 words", or a JSON structure your code parses.
- Stable background. Product facts, policies, today's date, the user's plan tier.
- Examples of good answers. OpenAI's guide lists identity, instructions, examples and context as the parts of a developer message.
What goes in the user message
- The specific request for this turn.
- Input data for that request: the text to summarise, the code to review, the ticket to classify.
- Anything that changes per request and should not affect later turns.
A useful test: if the instruction would be the same for every user and every turn, it belongs in the system prompt. If it belongs to this request only, put it in the user message. Never paste text from end users into the system prompt, because that would give their words operator-level priority.
Request JSON: Anthropic Messages API
Claude takes the system prompt as a top-level system field, separate from the messages array. This is the shape shown in Anthropic's prompting guide:
{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"system": "You are a helpful coding assistant specializing in Python.",
"messages": [
{"role": "user", "content": "How do I sort a list of dictionaries by key?"}
]
}
A multi-turn request sends earlier turns back in order. Anthropic's API reference says the models are trained on alternating user and assistant turns, and that consecutive turns with the same role are combined into one:
{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"system": "You are a helpful coding assistant specializing in Python.",
"messages": [
{"role": "user", "content": "How do I sort a list of dictionaries by key?"},
{"role": "assistant", "content": "Use sorted() with a key function: sorted(items, key=lambda d: d['name'])."},
{"role": "user", "content": "And in descending order?"}
]
}
On recent Claude models you can also add a {"role": "system"} message partway through the conversation. Anthropic's docs describe these mid-conversation system messages as a way to add an instruction that becomes relevant later without editing the top-level system field, which keeps the cached prompt prefix intact. Later system messages take precedence over earlier ones. They must follow a user turn and be either the last message or followed by an assistant turn; other positions return a 400 error. Anthropic lists the supported models in the docs; Claude Sonnet 5, for example, does not support them.
Request JSON: OpenAI Responses API
OpenAI's text generation guide shows developer and user roles in the input array (shown here as JSON; the guide's example is Python):
{
"model": "gpt-6-astra",
"reasoning": {"effort": "low"},
"input": [
{"role": "developer", "content": "Talk like a pirate."},
{"role": "user", "content": "Are semicolons optional in JavaScript?"}
]
}
The same API also has an instructions parameter, which the reference describes as "a system (or developer) message inserted into the model's context". The guide notes two details: instructions given this way take priority over the prompt in input, and they apply only to the current request. If you chain turns with previous_response_id, the instructions from earlier turns are not carried over, so send them again each time.
Local models
Local runtimes use the same idea. In Ollama, a Modelfile's SYSTEM instruction sets the system message used in the model's prompt template, for example SYSTEM """You are a concise assistant.""". Under the hood, every role is turned into special formatting tokens in one long text sequence, which is why the system prompt counts toward the context window on every turn like any other text.
Common mistakes
- Putting everything in the user message. It works, but the instructions lose their priority over what the end user types, and you repeat them on every turn.
- Putting per-request data in the system prompt. The prompt prefix then changes on every call, so prompt caching cannot reuse it.
- Treating the system prompt as a security boundary. It raises the priority of your rules; it does not make them unbreakable or secret. Enforce access control in your code, not in the prompt.
- Contradictory instructions. If the system prompt says "be brief" and a template in the user message says "explain in detail", the result is unpredictable. Decide which wins and say so.
For how to write the content of either message, see how to write a good prompt. If you are testing changes to a system prompt, generating a batch of realistic test inputs helps; larely's test data generator creates mock records as CSV or JSON in the browser.
Frequently asked questions
- Is the developer role the same as the system role?
- For practical purposes, yes. OpenAI's newer APIs use developer for instructions from the application, and its Responses API reference says instructions given with the developer or system role take precedence over the user role. Anthropic's API uses a top-level system field instead of a developer role.
- Can users see or override the system prompt?
- Users of your app do not see the system prompt unless you show it, but you should not treat it as secret: assume a user may be able to get the model to reveal or paraphrase it. Both OpenAI and Anthropic document that system or developer instructions take priority over user messages when they conflict, which makes overriding harder but not impossible. Never put passwords or API keys in a prompt.
- Does the system prompt count toward token usage?
- Yes. It is part of the input on every request, so a 2,000-token system prompt costs 2,000 input tokens each time. Because it is the same on every call, it is a good candidate for prompt caching, which both Anthropic and OpenAI offer for repeated prompt prefixes.
- What does the assistant role do in a request?
- Assistant messages are the model's earlier replies, which you send back so the model can see the conversation so far. OpenAI's reference says messages with the assistant role are presumed to have been generated by the model in previous interactions. On Anthropic's API, ending the messages with a partial assistant turn makes the model continue from that text, a technique called prefill, on models that support it.
Sources
- OpenAI API docs: Text generation (message roles and instructions)
- OpenAI API reference: Create a model response
- OpenAI API docs: Prompt engineering
- Claude API reference: Create a Message
- Claude API docs: Mid-conversation system messages
- Claude API docs: Prompting best practices
- Claude API docs: Prompt caching
- OpenAI API docs: Prompt caching
- Ollama docs: Modelfile (SYSTEM instruction)