In this guide, I'll add employeeBadgeNumber through a custom auxiliary object class named glcEmployee, using dsconf schema on instance ldap1 under dc=example,dc=com.
Extend the directory schema only when existing standard attributes and object classes cannot represent the required data. Many deployments already have employeeNumber on inetOrgPerson-style entries. Search the loaded schema before you allocate new OIDs.
Before you start:
- Install 389 Directory Server — running instance with Directory Manager or equivalent
dsconfaccess - 389 Directory Server architecture — how schema fits outside suffix data
- Manage users and groups — sample
user1entry underou=people - dsconf command cheat sheet — flag reference for
dsconf schema
dsconf schema and instance schema LDIF files. Entry syntax validation, bulk repair, and OpenLDAP schema import workflows belong in schema validation and repair. Replication overview covers replication agreement configuration. Here you only learn how schema consistency affects replicated data.
Tested on: Rocky Linux 10.2; 389 Directory Server 3.2.0.
Understand attributes and object classes
LDAP schema defines what directory entries may contain. Attribute types describe individual fields; object classes group those fields into entry shapes.
| Component | Purpose |
|---|---|
| Attribute type | Defines the name, syntax, matching behavior, and value rules |
| Object class | Groups attributes that entries can or must contain |
MUST |
Required attributes on entries using that class |
MAY |
Optional attributes permitted by that class |
| Structural class | Defines the primary type of an entry |
| Auxiliary class | Adds optional capabilities to an existing entry |
This guide uses an auxiliary class so you can extend user1 without replacing its existing posixAccount and nsPerson classes.
Plan the custom schema
Every custom attribute and object class needs a globally unique numeric OID. Production deployments must request or use an OID beneath your organization's IANA private enterprise number. Document your allocation range in an internal registry so two administrators do not publish the same number with different meanings.
This lab uses IANA's documentation-only PEN 32473 from RFC 5612. Do not use this branch in a production schema. Replace it with an OID beneath your organization's assigned PEN.
| Setting | Example |
|---|---|
| Attribute name | employeeBadgeNumber |
| Attribute OID | 1.3.6.1.4.1.32473.1.1.1 |
| Syntax | Directory String (1.3.6.1.4.1.1466.115.121.1.15) |
| Equality matching | caseIgnoreMatch (case-insensitive badge lookup) |
| Single-valued | Yes |
| Object class | glcEmployee |
| Object-class OID | 1.3.6.1.4.1.32473.1.2.1 |
| Object-class type | Auxiliary |
| Allowed attribute | employeeBadgeNumber (MAY) |
Inspect the existing schema
Search for an existing definition before you create a duplicate. A missing attribute query returns no definition line on this version. dsconf may also print a client-side error when the name is unknown:
dsconf ldap1 schema attributetypes query employeeBadgeNumberSample output:
Error: 'NoneType' object has no attribute 'names'Query a standard attribute to confirm what is already available. employeeNumber on inetOrgPerson is a common alternative to inventing a badge field:
dsconf ldap1 schema attributetypes query employeeNumber( 2.16.840.1.113730.3.1.3 NAME 'employeeNumber' DESC 'numerically identifies an employee within an organization' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'RFC 2798' )
MUST
MAY
( 2.16.840.1.113730.3.2.2 NAME 'inetOrgPerson' SUP organizationalPerson STRUCTURAL MAY ( audio $ businessCategory $ carLicense $ departmentNumber $ displayName $ employeeNumber $ employeeType $ givenName $ homePhone $ homePostalAddress $ initials $ jpegPhoto $ labeledURI $ mail $ manager $ mobile $ o $ pager $ photo $ roomNumber $ secretary $ uid $ userCertificate $ x500uniqueIdentifier $ preferredLanguage $ userSMIMECertificate $ userPKCS12 ) X-ORIGIN 'RFC 2798' )dsconf prints separate MUST and MAY headings. Because employeeNumber is optional on inetOrgPerson, that object class appears under the MAY section. This lab still adds employeeBadgeNumber to demonstrate a controlled custom extension when your application needs a separate field.
Confirm the object class name is unused:
dsconf ldap1 schema objectclasses query glcEmployeeNoneNone means no glcEmployee definition is loaded yet.
Create a custom attribute type
Directory String syntax (1.3.6.1.4.1.1466.115.121.1.15) stores UTF-8 text and is appropriate for badge identifiers such as GLC-1001. Syntax alone does not define how equality searches compare values. You also need an EQUALITY matching rule when applications filter on the attribute or when you build an eq index.
List available syntax OIDs when you need integers, booleans, or distinguished names:
dsconf ldap1 schema attributetypes get_syntaxesThe command prints the full catalog; the Directory String line is Directory String (1.3.6.1.4.1.1466.115.121.1.15).
Add the attribute with caseIgnoreMatch for case-insensitive badge lookups. Use caseExactMatch instead when capitalization must be significant. Do not add a substring matching rule unless applications genuinely need partial badge searches:
dsconf ldap1 schema attributetypes add employeeBadgeNumber --oid 1.3.6.1.4.1.32473.1.1.1 --desc "Employee badge identifier" --syntax 1.3.6.1.4.1.1466.115.121.1.15 --equality caseIgnoreMatch --single-value --x-origin "GoLinuxCloud lab schema"Successfully added the attributeTypeVerify the definition:
dsconf ldap1 schema attributetypes query employeeBadgeNumber( 1.3.6.1.4.1.32473.1.1.1 NAME 'employeeBadgeNumber' DESC 'Employee badge identifier' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN ( 'GoLinuxCloud lab schema' 'user defined' ) )
MUST
MAYSINGLE-VALUE tells the server to reject a second value on the same entry. EQUALITY caseIgnoreMatch enables (employeeBadgeNumber=…) filters.
Create a custom object class
Define the auxiliary class after the attribute exists. Auxiliary classes extend entries without replacing their structural class:
dsconf ldap1 schema objectclasses add glcEmployee --oid 1.3.6.1.4.1.32473.1.2.1 --desc "GoLinuxCloud employee extension" --kind AUXILIARY --sup top --may employeeBadgeNumber --x-origin "GoLinuxCloud lab schema"Successfully added the objectClassVerify:
dsconf ldap1 schema objectclasses query glcEmployee( 1.3.6.1.4.1.32473.1.2.1 NAME 'glcEmployee' DESC 'GoLinuxCloud employee extension' SUP top AUXILIARY MAY employeeBadgeNumber X-ORIGIN ( 'GoLinuxCloud lab schema' 'user defined' ) )Schema changes made through dsconf are persisted in /etc/dirsrv/slapd-ldap1/schema/99user.ldif on the tested host. Interactive adds take effect immediately for new LDAP operations on this instance.
Apply the custom schema to an LDAP entry
I'll add the auxiliary class and attribute to an existing user with ldapmodify. The DN must match the stored entry. This lab uses ou=people (lowercase) as created by dsidm:
cat > /tmp/glc-user1.ldif << 'EOF'
dn: uid=user1,ou=people,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: glcEmployee
-
add: employeeBadgeNumber
employeeBadgeNumber: GLC-1001
EOFApply the change over LDAPI as root:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -f /tmp/glc-user1.ldifmodifying entry "uid=user1,ou=people,dc=example,dc=com"The operation completes without an LDAP error when the schema and DN are correct. Depending on the client options, ldapmodify may print only the entry being modified.
Confirm the entry with ldapsearch:
ldapsearch -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -b "uid=user1,ou=people,dc=example,dc=com" "(objectClass=*)" objectClass employeeBadgeNumber# user1, people, example.com
dn: uid=user1,ou=people,dc=example,dc=com
objectClass: top
objectClass: nsPerson
objectClass: nsAccount
objectClass: nsOrgPerson
objectClass: posixAccount
objectClass: glcEmployee
employeeBadgeNumber: GLC-1001
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1glcEmployee appears alongside the existing person classes, and the badge value is stored on the entry.
Search by badge number to confirm the equality rule works:
ldapsearch -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -b "ou=people,dc=example,dc=com" "(employeeBadgeNumber=GLC-1001)" dn employeeBadgeNumber# user1, people, example.com
dn: uid=user1,ou=people,dc=example,dc=com
employeeBadgeNumber: GLC-1001
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1The filter returns user1. That shows the custom field can be searched, not merely displayed.
EQUALITY caseIgnoreMatch defines how Directory Server compares badge values. It does not create a database index. Create an eq index separately only when the attribute is searched frequently enough to justify one. See index design and maintenance when that chapter is available. An equality matching rule is required to support equality filters and equality indexes, but the index itself is a separate backend configuration.
Test a schema violation
Attributes are permitted only when an entry's object classes allow them. Remove glcEmployee from the entry, then try to add the badge value alone:
cat > /tmp/glc-strip.ldif << 'EOF'
dn: uid=user1,ou=people,dc=example,dc=com
changetype: modify
delete: objectClass
objectClass: glcEmployee
-
delete: employeeBadgeNumber
employeeBadgeNumber: GLC-1001
EOFI'll remove the auxiliary class and stored badge value so I can test schema enforcement on a plain user entry:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -f /tmp/glc-strip.ldifRe-add only the attribute without the auxiliary class:
cat > /tmp/glc-bad-attr.ldif << 'EOF'
dn: uid=user1,ou=people,dc=example,dc=com
changetype: modify
add: employeeBadgeNumber
employeeBadgeNumber: GLC-1001
EOFThis modify should fail because employeeBadgeNumber is not allowed until glcEmployee is present on the entry:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -f /tmp/glc-bad-attr.ldifldap_modify: Object class violation (65)
additional info: attribute "employeeBadgeNumber" not allowed
modifying entry "uid=user1,ou=people,dc=example,dc=com"The server rejects the attribute because no object class on the entry permits employeeBadgeNumber. Restore the working combination before you continue:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -f /tmp/glc-user1.ldifAdd a second badge value to see single-value enforcement:
cat > /tmp/glc-dup.ldif << 'EOF'
dn: uid=user1,ou=people,dc=example,dc=com
changetype: modify
add: employeeBadgeNumber
employeeBadgeNumber: GLC-2002
EOFDirectory Server should reject a second value because the attribute is marked SINGLE-VALUE:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -f /tmp/glc-dup.ldifldap_modify: Object class violation (65)
additional info: single-valued attribute "employeeBadgeNumber" has multiple values
modifying entry "uid=user1,ou=people,dc=example,dc=com"For deeper syntax checking across many entries, use schema validation and repair. This guide stops at one illustrative modify failure.
Update or remove custom schema definitions
dsconf schema exposes matching command families for maintenance:
dsconf schema attributetypes replace
dsconf schema attributetypes remove
dsconf schema objectclasses replace
dsconf schema objectclasses removeFollow this order when you retire a custom extension:
- Stop using the attribute in directory entries. Remove values and auxiliary classes from affected entries.
- Remove the attribute from dependent object-class
MUSTandMAYlists (or delete the object class). - Update or remove the object-class definition.
- Remove the attribute-type definition.
The server blocks removal while schema elements still reference each other. Removing employeeBadgeNumber while glcEmployee still lists it in MAY fails:
dsconf ldap1 schema attributetypes remove employeeBadgeNumberError: Server is unwilling to perform(53) - attribute type employeebadgenumber: Is included in the MAY list for object class glcEmployee. Cannot delete.After you clear entry data, remove the auxiliary class and attribute from user1:
cat > /tmp/glc-cleanup-user.ldif << 'EOF'
dn: uid=user1,ou=people,dc=example,dc=com
changetype: modify
delete: employeeBadgeNumber
-
delete: objectClass
objectClass: glcEmployee
EOFClear the badge value and auxiliary class from user1 before I remove the schema definitions:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -f /tmp/glc-cleanup-user.ldifRemove the schema definitions in dependency order:
dsconf ldap1 schema objectclasses remove glcEmployeeSuccessfully removed the objectClassAfter the object class is gone, I can remove the attribute type it referenced:
dsconf ldap1 schema attributetypes remove employeeBadgeNumberSuccessfully removed the attributetypeConfirm both definitions are gone:
dsconf ldap1 schema objectclasses query glcEmployeeNoneThe attribute query should also fail once the definition has been removed:
dsconf ldap1 schema attributetypes query employeeBadgeNumberError: 'NoneType' object has no attribute 'names'Do not delete schema definitions that entries still use. Clear entry data first, then shrink the schema.
Create a reusable custom schema file
The previous cleanup removed these definitions from 99user.ldif. The following file therefore becomes their only managed schema source on this lab instance.
Schema changes made through dsconf are persisted in 99user.ldif. The server can rewrite schema data in that file; treat it as the live store for interactive changes, not a simple append-only log.
For repeatable deployments across several instances, maintain a separate file under the instance schema directory:
/etc/dirsrv/slapd-ldap1/schema/99glc-custom.ldifDirectory Server loads schema files alphabetically. A custom filename must begin with two digits and sort before 99user.ldif. Use a name that also loads after every schema file containing definitions on which your custom schema depends. In this lab, 99glc-custom.ldif loads after the distribution schema files and before 99user.ldif.
Keep custom definitions in your own numeric prefix and leave distribution-managed files untouched. The file begins with the schema entry DN and carries RFC 4512 definitions:
dn: cn=schema
attributeTypes: ( 1.3.6.1.4.1.32473.1.1.1 NAME 'employeeBadgeNumber' DESC 'Employee badge identifier' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'GoLinuxCloud lab schema' )
objectClasses: ( 1.3.6.1.4.1.32473.1.2.1 NAME 'glcEmployee' DESC 'GoLinuxCloud employee extension' SUP top AUXILIARY MAY employeeBadgeNumber X-ORIGIN 'GoLinuxCloud lab schema' )Define the attribute before the object class that references it. You can publish the same file through configuration management to every supplier before entries that depend on the schema are written.
99glc-custom.ldif and 99user.ldif, because duplicate definitions make updates and replication harder to reason about.
Directory Server accepts custom schema through dsconf, the web console, or manually maintained LDIF files. The method should match how your team versions infrastructure.
Reload and verify schema files
After you edit schema LDIF on disk, reload the running server and wait for the task to finish:
dsconf ldap1 schema reload --waitAttempting to add task entry... This will fail if Schema Reload plug-in is not enabled.
Schema reload task (cn=schema_reload_2026-07-15T16:24:29.356062,cn=schema reload task,cn=tasks,cn=config) successfully finished.--wait blocks until the reload task completes instead of returning while the asynchronous job is still running. If the Schema Reload plug-in is disabled, the task fails. Enable the plug-in before you rely on file-based updates, and check the instance error log when a reload does not finish.
Confirm the definitions are visible to clients:
dsconf ldap1 schema attributetypes query employeeBadgeNumberI'll query the auxiliary object class as well to confirm the file reload picked up both definitions:
dsconf ldap1 schema objectclasses query glcEmployeeBoth commands should print the employeeBadgeNumber and glcEmployee definitions after reload. If ldapmodify reports unknown object class immediately after a file-only change, run dsconf INSTANCE schema reload --wait before retrying the modify.
Maintain schema across replicated servers
Schema added through dsconf is persisted in 99user.ldif with an nsSchemaCSN. Directory Server does not send the change immediately. When the next data update is replicated, the supplier compares schema CSNs and can transfer its compatible schema before sending entries that depend on it.
For reliable schema management, choose one approach:
- Manage the schema through
dsconfand99user.ldif, allowing compatible schema updates to propagate through replication. - Maintain a separate file such as
99glc-custom.ldif, deploy the identical file to every server, and reload it everywhere before writing dependent entries.
Schema definitions kept outside 99user.ldif remain local files and should be deployed consistently to every applicable server; only 99user.ldif participates in normal schema-CSN-based automatic propagation.
Files other than 99user.ldif remain local and are not automatically copied to replication partners. Automatic schema propagation requires every relevant attribute type and object class on the supplier to be a compatible superset of the corresponding definition on the consumer. If the supplier definition is a subset, or the two definitions would need to be merged, Directory Server does not automatically propagate the schema. Avoid making independent, conflicting schema changes on different suppliers.
Confirm schema parity on every supplier before production writes when you add custom attributes that clients depend on. Supplier and consumer configuration lives in replication overview and the dedicated replication chapters. Schema mismatch between peers often appears as replication errors or object class violation on the consumer. Compare dsconf schema output on each supplier when that happens.
Troubleshoot custom schema errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Attribute or object class already exists | Duplicate dsconf schema … add |
Query the existing definition; use replace only when you intend to change it |
| Duplicate OID error on add | OID already assigned to another definition | Choose a new OID from your documented branch; never reuse OIDs for different meanings |
| Unknown syntax OID | Invalid --syntax value |
Run dsconf schema attributetypes get_syntaxes and copy a supported OID |
| Missing superior object class | Invalid --sup on object-class add |
Query a parent class with dsconf schema objectclasses query and reference a loaded name or OID |
| Attribute not allowed on modify | Entry lacks an object class that permits the attribute | Add the auxiliary class before the attribute, or extend MAY on the object class |
Equality search fails with PROTOCOL_ERROR or UNWILLING_TO_PERFORM |
The custom attribute has no compatible EQUALITY matching rule |
Add a matching rule compatible with the syntax, such as caseIgnoreMatch or caseExactMatch, then reload and retry |
Missing MUST attribute |
Structural rules not satisfied | Add required attributes, or change the object-class MUST list before creating entries |
unknown object class after file edit |
Running server has stale schema | Run dsconf INSTANCE schema reload --wait and recheck error logs |
| Cannot remove attribute or class | Still referenced by entries or other schema | Clear entry values, remove MAY/MUST references, then delete definitions |
| Replication consumer rejects custom entries | Schema differs between suppliers | Align schema files and reload on all suppliers before replicating new data |
Summary
- Reuse standard schema whenever an existing attribute such as
employeeNumberfits the requirement. - Allocate unique OIDs from your organization's PEN branch, or RFC 5612
32473for documentation labs only. - Define the attribute type with syntax and equality matching before the object class that references it.
- Prefer an auxiliary class when you extend existing person entries.
- Test adds, equality searches, and intentional violations on sample entries before production rollout.
- Keep schema definitions consistent on every supplier that will hold or replicate the data.
What's Next
- Manage users and groups — assign custom object classes to entries
- ACI examples — protect new attributes with access rules
- Suffixes and backends — schema applies per instance suffix
References
- Red Hat Directory Server 13 — Management, configuration, and operations — Chapter 7 covers schema planning,
dsconf, files, reload, validation, and replication - IANA Private Enterprise Numbers — verify or request an organizational PEN
- RFC 5612 — documentation-only enterprise number 32473
- RFC 4512 — LDAP directory information models and schema description format
- dsconf(8) manual —
schemasubcommand family

