OCSF Contribution Guide
September 2, 2026 · View on GitHub
Rajas Panat, July 2022; Updated September 2026.
Contributions by Mike Radka, Donovan Kolbly.
This documentation presents guidelines and expected etiquettes to successfully contribute to the development of OCSF Schemas and the framework itself.
Pull Request Guidelines
All contributors must submit their changes via pull requests. If you're not familiar with pull requests, please read the GitHub documentation. Following are a few guidelines and expectations for submitting a PR.
- Fork ocsf-schema. For changes to ocsf-docs or ocsf-server, follow that repository's contributing docs.
- Make the desired changes in the forked repo and verify they work before opening a PR.
- Push the changes to the forked repo.
- Create a Pull Request to merge into
ocsf/ocsf-schema. You'll need at least 3 approvals to get the PR merged. - Follow the guidelines in the PR template.
- Keep the PR focused: a single PR must contain related changes.
- Each commit must include a DCO Developer's Certificate of Origin.
- Add a numbered entry to the matching subsection of
Unreleasedin CHANGELOG.md (Added,Improved,Deprecated,Misc, and so on). Include the PR link once the PR exists. - Describe your change in as much detail as possible.
- Confirm that you have tested the changes, and the local verification run was error free.
- Check the Preview tab to ensure everything looks as expected.
- Once the PR is ready, add relevant labels and submit it.
- Resolve any GitHub Action failures or warnings on your pull request, and stay involved in the conversation.
- Thank you for your contribution!
Key OCSF Terminology
- Field: A field is a unique identifier name for a piece of data contained in OCSF. Each field also designates a corresponding data_type.
- Object: An object is a collection of contextually related fields and other objects. It is also a data_type in OCSF.
- Attribute: An attribute is the more generic name for both fields and objects in OCSF. A field is a scalar attribute while an object is a complex attribute.
- Event Class: An event is represented by an Event Class, which are a particular set of attributes (including fields and objects) representing a log line or telemetry submission at a point in time.
- Category: A Category organizes event classes that represent a particular domain.
- Profile: A mix-in set of attributes that can be applied to event classes and objects across categories, so the same extra context does not require new classes.
More details about OCSF concepts, terminology and use-cases can be found in Understanding OCSF.
Schema changes
Adding an event class, in brief
- Determine all the
attributes(including fields and objects) you would want to add in theevent_class. - Check dictionary.json and the objects folder; many of your desired attributes may already be present.
- Define the missing attributes → Adding/Modifying an
attribute - Determine which category you would want to add your event_class in, note its
name - Create a new file →
<event_class_name.json>inside the category specific subfolder in the events folder. Template: templates/event_class_name.json - Define the
event_classitself → Adding/Modifying anevent_class - Finally, verify the changes before opening a PR.
Adding/Modifying an attribute
-
All the available
attributes—fieldsandobjectsin OCSF — are and will need to be defined in the attribute dictionary, dictionary.json, and the objects folder if defining an object. -
Determine if a new attribute is required for your change; it might already be defined in the attribute dictionary and/or the objects folder.
-
Before adding a new attribute, review the following OCSF attribute conventions:
- Attribute names must be a valid UTF-8 sequence.
- Attribute names must be all lower case.
- Combine words using underscore.
- No special characters except underscore.
- Use present tense unless the attribute describes historical information.
- Use singular and plural names properly to reflect the field content. Example: use
events_per_secrather thanevent_per_sec. - When attribute represents multiple entities, the attribute name should be pluralized and the value type should be an array. Example:
process.loaded_modulesincludes multiple values — a loaded module names list. - Avoid repetition of words. Example:
src_endpoint.src_ipshould besrc_endpoint.ip. - Avoid abbreviations when possible. Some exceptions can be made for well-accepted abbreviation. Example:
ip,os,cveetc.
Caption & description style (normative & neutral)
To keep OCSF suitable for international standardization (e.g. ITU ratification), captions and descriptions must be self-contained, vendor-neutral, and free of personal or politically-charged framing. An automated reviewer comments on PRs that don't follow these rules; the same rules are summarized here so you can get them right up front.
- No authoritative URLs in captions or descriptions. Refer to the source by name (e.g.
RFC 1035,MITRE ATT&CK,ASTM F3411-22a) and put the link in the container'sreferencesarray. URLs are fine only as illustrative example values, never as the citation for a field. - No trademark/registered marks (®, ™, ℠) in captions or descriptions. Use generic wording and keep the marked, proprietary name in
referencesonly. Seeobjects/attack.jsonfor the canonical pattern. (The canonical proprietary-source objects —attack,atlas,d3fend,d3f_tactic,d3f_technique— may keep the mark in their own primary caption.) - No company/vendor/product names in captions or descriptions. Use the generic technology or standard, and keep any vendor name to
examplesonly. Product names that are the modeled domain inside a platform extension (e.g. Windows registry concepts underextensions/windows/) are an exception. - Keep descriptions technical, not personal or political. Write descriptions as data definitions, not as descriptions of a real person or a nation/state. Avoid anthropomorphic/biographical framing, personal or protected-class characterization (race, religion, health, citizenship, political affiliation, etc.), and politically-charged wording. This does not ban legitimate entities —
user,account,identity,actor, andpersonremain valid technical terms, and geographic values stored as data (e.g. an ISO country code) are fine. When phrasing drifts toward describing a human, reframe toward the account/identity/principal — e.g. prefer "the user account and its security-relevant attributes" over "the user and what they personally like to do".
How to define a field in the dictionary?
To add a new field in OCSF, you need to define it in dictionary.json as described below.
Sample entry in the dictionary:
"uid": {
"caption": "Unique ID",
"description": "The unique identifier. See specific usage.",
"type": "string_t"
}
Choose a unique field you want to add, uid in the example above and populate it as described below.
-
caption→ Add a user friendly name to the field. -
description→ Add a concise description to define the attributes.- Note that
fielddescriptions can be overridden in theevent_class/object, therefore if it's a common field (like name, label, uid etc) feel free to add a generic description; specific descriptions can be added in theevent_class/objectdefinition. For example, - A generic definition of
uidin the dictionary:uid:The unique identifier. See specific usage.
- Specific description of
uidin thevulnerabilityobject:uid:Unique Identifier/s of the reported vulnerability. e.g. CVE ID/s
- Note that
-
type→ Review OCSF data_types and ensure you utilize appropriate types while defining new fields.- All the available data_types can be accessed on schema.ocsf.io/data_types.
- They are also accessible in your local instance of the ocsf-server: http://localhost:8080/data_types
-
is_array→ This is a boolean key:value pair that you would need to add if the field you are defining is an array.- e.g.
"is_array": true
- e.g.
-
Integer enums → Normalized enumerations are
integer_tattributes whose names end in_id(arrays:_ids). Declare a stringsibling(usually the same name without_id, for exampleseverity_id/severity) and anenummap. Include0Unknown and99Other unless you are matching an existing documented exception. Classification siblings use_name(class_uid/class_name)."severity_id": { "caption": "Severity ID", "description": "The normalized identifier of the event/finding severity.", "sibling": "severity", "type": "integer_t", "enum": { "0": { "caption": "Unknown", "description": "The severity is unknown." }, "99": { "caption": "Other", "description": "The severity is not mapped. See the `severity` attribute." } } }
How to define an object?
-
All the available
objectsneed to be defined as individual field entries in dictionary.json and as distinct.jsonfiles in the objects folder. -
Review existing Objects, determine if a modification of the existing object would be sufficient or if there's a need for a completely new object.
-
Use templates/object_name.json to get started with the
.jsonfile definition.An example
vulnerability.jsonobject file:{ "caption": "Vulnerability Details", "name": "vulnerability", "description": "The vulnerability object describes details related to the observed vulnerability.", "extends": "object", "attributes": { "desc": { "description": "The description of the vulnerability", "requirement": "recommended" }, "kb_article_list": { "requirement": "optional" } } } -
caption→ Add a user friendly name to the object. -
description→ Add a concise description to define the object. -
extends→ Ensure the value isobjector an existing object, e.g._entity(All objects in OCSF must extend a base definition ofobjector another existing object.) -
name→ Add a unique name of the object.namemust match the filename of the actual.jsonfile. -
attributes→ Add the attributes that you want to define in the object,-
requirement→ For each attribute ensure you add a requirement value. Valid values areoptional,required,recommended -
$include→ You can include attributes from other places; to do so, specify a virtual attribute called$includeand give its value as the list of files (relative to the root of the schema repository) that should contribute their attributes to this object. e.g."attributes": { "$include": [ "profiles/host.json" ] }
-
Note: If you want to create an object which would act only as a base for other objects, you must prefix the object name and the actual json filename with an _. The resultant object will not be visible in the OCSF Server. For example, take a look at the _entity object.
Sample entry in dictionary.json:
"vulnerability": {
"caption": "Vulnerability",
"description": "The vulnerability object describes details related to the observed vulnerability.",
"type": "vulnerability"
}
Choose a unique object you want to add, vulnerability in the example above and populate it as described below.
caption→ Add a user friendly name to the objectdescription→ Add a concise description to define the object.type→ Add the type of the object you are defining.is_array→ This is a boolean key:value pair that you would need to add if the object you are defining is an array.- e.g.
"is_array": true
- e.g.
Adding/Modifying an event_class
- All the available Event Classes are defined as
.jsonfiles in the events folder. - Review existing Event Classes, determine if a modification of the existing class would be sufficient or if there's a need for a completely new event_class.
- To define a new class:
-
Create a new file →
<event_class_name.json>inside the category specific subfolder in the events folder. -
Use templates/event_class_name.json to get started with the
.jsondefinition. -
uid→ Select an integer in the range 0–99. Ensure the integer is unique within the category.- Note: Without
uid, an event_class won't be visible in the ocsf-server. - Do not set
class_uidortype_uidby hand; the compiler derives them (class_uid=category_uid * 1000 + uid).
- Note: Without
-
caption→ Add a user friendly name to the event_class. -
description→ Add a concise description to define the attributes. -
name→ Add a unique name of the event_class. Ensure it matches the file name to maintain consistency. -
extends→ Use the category class (network,system,finding, and so on) so the new class inherits that category's attributes. Only a new category root should extendbase_event. For example,events/network/http_activity.jsonextendsnetwork. -
attributes→ Add the attributes that you want to define in the event_class,-
group→ For each attribute ensure you add a group value. Valid values areclassification,context,occurrence,primary -
requirement→ For each attribute ensure you add a requirement value. Valid values areoptional,required,recommended -
$include→ As for objects, you can also include attributes from other places; to do so, specify the list of files (relative to the root of the schema repository) that should contribute their attributes to this object. e.g."attributes": { "$include": [ "profiles/cloud.json" ] }
-
-
constraints→ For each class you can add constraints on the attribute requirements. Valid constraint types areat_least_one,just_one. e.g."constraints": { "at_least_one": [ "uid", "name" ] }A Constraint is a documented rule subject to validation that requires at least one of the specified recommended attributes of a class to be populated.
-
Adding/Modifying a profile
A profile is a mix-in set of attributes that can be applied to event classes and objects across categories. Use a profile when the same extra context (host, cloud, security control, container, and so on) should be optional on many classes, instead of creating new classes. For design background, see Profiles are Powerful.
Review existing files in profiles before adding a new one. Applying an existing profile to a class or object is often enough.
Define a profile
- Every attribute in the profile must already be defined in dictionary.json (and objects if it is an object).
- Create
profiles/<name>.json.namemust match the filename. Template: templates/profile_name.json. - Required keys:
caption→ A short, user-friendly name.description→ A concise description of when to apply the profile.meta→ Must beprofile.name→ A unique name, lowercase, words joined with underscore.attributes→ Each attribute needs arequirement(optional,required, orrecommended). You may overridedescriptionorgrouphere.
- Optional
annotations.groupsets a default group for attributes that do not specify one. See profiles/host.json.
Example profiles/cloud.json:
{
"caption": "Cloud",
"description": "The attributes that describe information specific to Cloud services/applications.",
"meta": "profile",
"name": "cloud",
"attributes": {
"api": {
"group": "context",
"requirement": "optional"
},
"cloud": {
"group": "primary",
"requirement": "required"
}
}
}
The datetime profile is special: it has an empty attributes map, and the compiler adds datetime_t siblings next to timestamp_t fields. Do not copy that pattern for ordinary profiles.
Apply a profile to a class or object
Always list the profile name in the class or object's profiles array so events can be filtered by metadata.profiles. Then pick one of these patterns:
-
Augment (most common).
$includethe profile file so its attributes are mixed in when the profile is applied. See events/network/network.json."profiles": ["cloud"], "attributes": { "$include": [ "profiles/cloud.json" ] } -
Native. The class already defines the profile's attributes. List the profile in
profiles, but do not$includeit. You can still overridegrouporrequirementon those attributes. -
Partially native.
$includethe profile, then set"profile": nullon attributes that must remain even when the profile is not applied. Withoutprofile: null, turning the profile off in the browser would hide those native attributes. Seeactoranddeviceon events/system/system.json."actor": { "group": "primary", "requirement": "required", "profile": null } -
Hybrid. Some attributes on an object should appear only when a profile is applied to the enclosing class, but they are not part of the profile definition itself. List the profile in
profilesand set"profile": "<name>"on those attributes. Do not$includethe profile on the object, or you would duplicate the profile's class-level attributes onto the object. Seecloud_partition,region, andzoneon objects/resource_details.json."cloud_partition": { "profile": "cloud", "requirement": "optional" }
Apply a profile at the category class when every class in that category should offer it. Apply it on events/base_event.json only when it is valid on essentially every class.
Platform extension profiles live under extensions/<platform>/profiles/ (for example extensions/linux/profiles/linux_users.json).
Deprecating an attribute, object, class, or enum value
The @deprecated annotation can be added to a dictionary attribute, an attribute used within a class/object/profile, a whole object, a whole event class, or an individual enum value. Follow the steps below:
-
Create a GitHub issue, explaining why the item needs to be deprecated and what the alternate solution is.
-
Add the
@deprecatedannotation as a JSON property of the item that is the subject of deprecation."@deprecated": { "message": "Use the <code>ALTERNATE_ATTRIBUTE</code> attribute instead.", "since": "semver", "superseded_by": ["ALTERNATE_ATTRIBUTE"] }message(required) — a human-readable explanation. Name the replacement in a<code>tag using its actual name (not its display caption), so the text matches the machine-readablesuperseded_by. Cite a path in one<code>tag:Use the <code>compliance.checks</code> attribute instead., notUse the <code>checks</code> array in the <code>compliance</code> object instead.Add any extra guidance as a following sentence.since(required) — the version after which the item is deprecated.superseded_by(required) — a machine-readable list of the replacement(s). List every concrete replacement. When the item is removed with no successor, use an empty array[]to state that explicitly.
-
What
superseded_bymay reference (use the form that matches where the replacement lives):- A dictionary attribute name (e.g.
["application"]), or the name of another attribute in the same class/object (e.g.["resources"]). - A fully-qualified path into another object or class — e.g.
["job_action.cmd_line"],["email.uid"],["dns_activity.opcode"], or["cpu_info_list[*].cores"]. - A class or object name, when deprecating a whole class/object — e.g.
["finding_info"]or["evidence_info"]. - Another enum value's key, when deprecating an enum value — e.g.
["8"]. - Multiple replacements are allowed — e.g.
["labels", "tags"]or["cpu_info_list[*].model", "cpu_info_list[*].vendor_name"].
Arrays. When the replacement is a field inside an array of objects, mark the array with
[*]and name the field —["related_cwes[*].uid"], not["related_cwes"]. Reference the array alone only when the whole array is the replacement.Caution — bare names vs. qualified paths. A bare attribute name marks the replacement globally: the schema browser will show a "Supersedes" note on that attribute everywhere it is used. Only use a bare name when the replacement genuinely is the schema-wide successor (e.g.
tag→labels). When the replacement is context-specific, use the fully-qualified path instead — e.g.cpu_type(a CPU field) must reference["cpu_info_list[*].vendor_name"], not["vendor_name"]; the bare form would incorrectly stamp the note onvendor_namein every unrelated object (vulnerability, product, agent, ...) that shares that generic name. - A dictionary attribute name (e.g.
-
How the schema browser renders it (the compiler resolves
superseded_byin browser mode):- Same-scope replacement (a dictionary attribute, another attribute in the same class/object, a class/object name, or another enum value of the same attribute) → a "Supersedes" note is shown by default on the replacement, pointing back to the deprecated item.
- Cross-container replacement (a dotted path into a different object/class) → a "Replaced by" link is shown on the deprecated item, linking to the replacement's page.
-
Example of a deprecated field (dictionary attribute)
"packages": { "@deprecated": { "message": "Use the <code>affected_packages</code> attribute instead.", "since": "1.0.0", "superseded_by": ["affected_packages"] }, "caption": "Software Packages", "description": "List of vulnerable packages as identified by the security product", "is_array": true, "type": "package" } -
Example of a deprecated object
{ "caption": "Finding", "description": "The Finding object describes metadata related to a security finding generated by a security tool or system.", "extends": "object", "name": "finding", "@deprecated": { "message": "Use the new <code>finding_info</code> object.", "since": "1.0.0", "superseded_by": ["finding_info"] }, "attributes": {} } -
Example of a deprecated enum value (
superseded_byreferences the replacement value's key)"9": { "caption": "REG_QWORD_LITTLE_ENDIAN", "@deprecated": { "message": "Use <code>REG_QWORD</code> instead.", "since": "1.6.0", "superseded_by": ["8"] } } -
Example of an item removed with no replacement (empty
superseded_by)"obsolete_field": { "@deprecated": { "message": "This field is obsolete and has no replacement.", "since": "1.6.0", "superseded_by": [] }, "caption": "Obsolete Field", "type": "string_t" }
Verifying the changes
Before you open a PR, confirm the schema is valid and compiles. Pull requests to main run metaschema validation, a lean compile, and a backwards-compatibility check. Spellcheck runs on PRs as a warning only.
Install the tools with pip in a virtual environment. The compiler requires Python 3.14+. The validator needs Python 3.11+ (3.14 works).
-
From the schema repository root, run the metaschema validator (ocsf-validator):
pip install "ocsf-validator>=0.2,<0.3" python -m ocsf_validator .FATAL and ERROR findings fail the command. WARNINGs do not; still fix warnings that are about your change.
-
Compile the schema (ocsf-schema-compiler). JSON is written to stdout and logs to stderr — redirect the JSON, or it will flood your terminal. This lean compile is what CI runs:
pip install ocsf-schema-compiler ocsf-schema-compiler . > /dev/nullA typical compile failure looks like:
Attribute "event_uid" from "HTTP Activity" is not a defined dictionary attribute Compilation failed: ... -
To browse locally, compile in browser mode (
-b) and point ocsf-server v4+ at that file viaSCHEMA_FILE. Lean JSON will not work in the server. Follow the ocsf-server README.ocsf-schema-compiler . -b > compiled-browser.jsonThe server should start with no errors.
CI also runs python -m ocsf.validate.compatibility (from ocsf-lib) against the last stable release. You do not need to run that locally unless you are changing classification IDs or removing attributes.
Address every compile error and validator ERROR before submitting.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or
(b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or
(c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.
(d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved.
We require that every contribution to this repository is signed with a Developer Certificate of Origin. Additionally, please use your real name. We do not accept anonymous contributors nor those utilizing pseudonyms.
Each commit must include a DCO which looks like this:
Signed-off-by: Jane Smith <jane.smith@email.com>
You may type this line on your own when writing your commit messages. However, if your user.name and user.email are set in your git configs, you can use -s or --signoff to add the Signed-off-by line to the end of the commit message.
OCSF Extensions
OCSF can be extended with additional attributes, objects, profiles, event classes, and categories. Each public extension has a unique name and uid so it does not collide with the core schema or other extensions.
Vendor extensions are registered in this repository; the extension schema itself is not added under extensions/. (The Linux, Windows, and macOS platform extensions already live there.)
Where to host a vendor extension
Registration reserves a name and UID. The schema files live in a separate repository. You can:
- Host it in the OCSF GitHub organization. Ask
@ocsf/ocsf-maintainersabout creating a repo under github.com/ocsf. Public examples include ocsf/aws, ocsf/splunk, ocsf/symantec, and ocsf/dev-ext. - Host it in your own organization (or any other public git host).
- Keep it private. You can still register a name and UID so they are reserved. Leave the registry
Repositorycell empty.
Use the same directory layout as the platform extensions; see extensions/README.md. Compile a vendor extension with the core schema using ocsf-schema-compiler path/to/ocsf-schema -e path/to/your-extension.
If the code is public, add a link in the registry's optional Repository column.
Register a vendor extension
To reserve a name and UID, open a pull request that only updates the registry. Follow #1701 as the example.
- Add a row to the OCSF Extensions Registry with a unique caption, name, UID, a short notes line, and an optional
RepositoryURL. Take the next unused vendor UID (the registry counts down from 998). - Add an
Unreleasedentry in CHANGELOG.md, for example:New Extension registration for <Vendor>. - Open a pull request using the PR template, and request reviewers as usual.
- Email info@ocsf.io from an official company address so maintainers can confirm the request is authorized by the organization it represents.
Example registry row:
| Caption | Name | UID | Notes | Repository |
|---|---|---|---|---|
| Vendor Name | vendor | 123 | The Vendor schema extension |
Looking to contribute to OCSF Server?
See the ocsf-server project documentation.