Skip to content
XBSL (1C:Element)
English
Esc
↑↓navigate↵open⌘Jpreview
On this page

Metadata scaffolding

Creating objects, attributes, routes and forms through the engine instead of hand-writing yaml – and the code templates.

Writing yaml by hand means remembering a UUID, the property names of a kind and where a form is registered. The engine does that part, and the same operations are available from the CLI, an editor and an agent.

Metadata scaffolding

The toolkit takes over the metadata mechanics: UUIDs, indentation, precise yaml insertions, duplicate checks and the compatibility of a section with a kind. The same operations show up in three places. In the CLI they are subcommands with JSON output. In MCP they are the meta_* tools for agents. In LSP they are the custom xbsl/meta* requests behind the VS Code metadata tree.

33 kinds of project element can be created: from Catalog and Document to VirtualTable paired with its mandatory .xbql query, ScheduledJob, contracts, rights and commands. Each kind carries what the docs make mandatory. That means the platform’s own default scope, InSubsystem - widen it deliberately with --scope. It also means a module stub for the handler the kind cannot live without, and a note about whatever the generator must not invent for you. Kinds whose content is drawn in the designer, ReportPanel and IntegrationProcess, are deliberately absent.

The VS Code tree, AI agents and the terminal call the same scaffolding core; it writes created and point-edited yaml/xbsl files, the linter checks what was written, and the response carries files, notes and the lint report; the LSP surface returns full texts for the editor to apply

xbsl new-project . vendor App                          # descriptor + module + a subsystem
xbsl new-object <subsystem-dir> <kind> <name>          # kind: Catalog, Document, Enum, ... (--help spells them)
xbsl new-object <subsystem-dir> <kind> <name> --presentation <plural> --object-presentation <singular>
xbsl add-field <object>.yaml <item-kind> <name> --type <type>  # item kind: реквизит, ... (--help lists them)
xbsl add-form . --name <object>                        # object + list forms, registered
xbsl add-form . --name <object> --forms list-cards     # list form as a card grid
xbsl new-object <subsystem-dir> <http-service-kind> <name> --routes "GET /, POST /, GET /{id}"
xbsl add-route  <service>.yaml "DELETE /{id}"          # url template + handler stub
xbsl add-method <module>.xbsl <method> --annotations <annotation> --after <existing-method>
xbsl set-localization <strings>.yaml <key> --value <language>=<text> ...  # one row, every language
xbsl add-subsystem vendor/App <name>
xbsl add-dependency . acme CurrencyConverter 2.0       # attach a library to the project
xbsl rename-object . <old-name> <new-name>             # rename files + update references
xbsl delete-object . --name <object>                   # the plan; --apply deletes and lists leftovers
xbsl move-object . <object>.yaml <package-dir>         # into a package: files + imports + full names
xbsl rename-package . <package-dir> <new-name>         # the folder + imports + full names
xbsl move-resource . <resource> <folder>               # into another folder: files + keys
xbsl rename-resource-folder . <folder> <new-name>      # the folder + the keys of its files
xbsl delete-resource-folder . <folder>                 # the plan; --apply deletes and lists references
xbsl resource-references . <resource>                  # the places that name a resource
xbsl set-access . --name <object> --default <access-method>
xbsl object-info . --name <object>                     # fields, tabulars, forms, namespace
xbsl project-info .                                    # projects, subsystems, objects by kind
xbsl project-info . --package <package>                # the objects of one package

The object kind is taken in either spelling, and xbsl new-object --help lists the kinds a project can hold. The item kind of add-field and set-field-property is a word of the tool itself: реквизит, табличная-часть, строка and the rest that xbsl add-field --help lists. Each has an English twin for a project written in English, the platform’s own term for the same item - attribute, tabular-part, string - and case and hyphens do not matter. The access methods and rights of set-access are taken in either language too, the way a yaml file of that language spells them: PermitAuthenticated or its Russian name, Read or Чтение. The annotations and every identifier reach the CLI in the spelling the platform uses for the project’s development language. The prose of this page names them by their English equivalents - Catalog, Attributes, OnServer - which is why the examples above use placeholders.

--presentation writes the caption of the element where its kind keeps it. A catalog, a document, an exchange plan and a settings storage keep two captions in the Interface section: --presentation is the list caption, in the plural, and --object-presentation the object caption, in the singular (MCP presentation and object_presentation). The tool cannot derive one from the other, and naming/presentation asks for both, so an object created with both lints clean. Any other kind refuses --object-presentation. An information register pairs its list caption with the record one, --record-presentation (MCP record_presentation), which no other kind takes. A constants set has one caption, the record one, and --presentation writes it. So does a processing: its caption belongs to the Interface section itself. --periodicity (MCP periodicity) makes a constants set periodic - Day, Month, Quarter or Year - and a periodic set has a list beside its record, so it is captioned the way an information register is: --presentation for the list, --record-presentation for the record.

A property the kind is born with goes in only where the compatibility mode of the project knows it. An access key is born computed (ManualGrant: False, with the stub of its handler): a project in the newest mode refuses a key that does not choose its flavour, and a mode that predates the property gets the key without the line and a note saying why.

project-info answers narrowly. --kind, --subsystem and --package narrow the list of objects, --brief leaves the lists out and keeps the counts, and --project walks only the named project - by Name, Vendor::Name or its folder, since a repository root often holds examples beside the project. Every object carries its subsystem, its package (empty at the subsystem root, Batches or Batches::Archive inside one) and the full namespace, Vendor::Project::Subsystem::Package - the prefix the generated forms use for their row types. packages lists the packages of the listed objects with their folders. The object kinds, the section kinds and the access methods come with --reference or --brief: they do not depend on the sources.

The sources themselves may be written in either language, and the scaffolding reads both. An object whose file spells its kind and its sections in English - ElementKind: Catalog, Attributes: - is found, described and edited exactly like one spelled the other way; see demo-en/ for such a project. What the tool writes follows the file it writes into: a new attribute of an English object gets Name: and Type:, a missing section is created as Attributes:, a subsystem and a library entry are spelled like the project around them. The pairs come from the platform data, from the metamodel’s own English name of every property. So a name the platform spells differently depending on the class keeps its original spelling in both reading and writing, with nothing left to guess: the enumeration values section is Items there and Elements elsewhere. In a value, the names of the author stay as given and the words of the platform follow the file: the platform types of a type expression, an access method (PermitEveryone in an English file, its Russian name in a Russian one). A boolean an MCP call passes as true or false is written in the word of the file’s language: Истина and Ложь in a Russian file, True and False in an English one, as the sources of the distribution write it.

add-field puts a new item at the end of the section of its kind, and creates that section only when the file has none. A register keeps its data in Dimensions and Resources. So an attribute asked of a register that holds resources and no attributes lands in a new Attributes section at the end of the file. The notes field says so and names the kind that would have placed the field beside the existing ones: a resource here, and the other way round for a resource asked where only attributes exist.

An interface component takes its own properties the same way. The property kind writes a name and a type into Properties, with no Id: the class of a component property has none. --prop adds the three keys the documentation lists for such a property: DefaultValue, StoredData and Contextual (--prop DefaultValue=0). A component without the section gets it where the designer writes it, after Inherits and in front of Events. The event kind fills the component’s own Events the same way: a name and the type of the event object, ComponentEvent when --type is not given, or EventWithData<String> and the like for an event that carries a value. A missing Events section goes right after Properties, or at the end of the file when the component has no properties. --doc gives an item a description, and the text becomes its documentation comment: the ## lines at the head of the item, the only comment the development environment keeps when it writes the file out.

Forms are generated with real content: input fields per attribute, including the standard Name, Number and Date fields and hierarchy support, DynamicList columns, TabularParts tables and a report form with parameters. The form is registered in the Interface section of its owner. --dry-run prints the changes with full file texts and writes nothing. That is the flag the VS Code extension uses: it applies the changes itself, through its own undo-friendly edits.

The captions of the form and of its columns go through the project’s dictionary. When the subsystem folder holds one LocalizedStrings element and the descriptor declares two localization languages, a caption is written as $Dictionary.Name. The keys those references need join the dictionary, and the translations it already has, in the same operation: a reference to a key nobody declares fails the apply. Without such a dictionary the caption stays a literal.

--forms list-cards builds the list form as a card grid instead of a table: a CustomList whose RowsContainer is a MatrixGroup, plus a generated ListRow<Name> row component named after the object. The card takes a Title, a photo and up to three more fields, with dates formatted. An attribute of type BinaryObject.Reference switches the card to CustomCard, with the image above the caption. The notes field reports what landed on the card and what did not. --card-min-width sets the grid column width, 400 by default and 250 with a photo, and --card-placeholder sets the image shown when the photo is empty.

set-localization writes one localized string into every language at once. The text of the default language goes into the LocalizedStrings element itself, which is where the platform keeps it; every other language gets its own Localization/<Code>/<Name>.yaml. This was the missing half of add-localization, which adds a language. A caption used to be typed into the element and again into its English twin, the two files drifted apart, and nothing but a pair of eyes could compare them. A language named without a translation file is refused: add the language first. An existing language the call says nothing about still gets the row, with the default text and a note, so no translation is left a key short. A key keeps the section it already lives in, and a new one goes to Rows unless --section says Templates.

--dry-run here is unlike the rest of the family: a summary by row (key, language, file, the text before and after) rather than the whole files, because one key on a two-language project used to print over 100 KB to report a one-line change. --full-text asks for the files back next to the summary. --entries-file <json-or-yaml> writes many keys in one call - {Key: {Language: Text}} - and --entry Key={"Language":"Text"} adds one more on the command line; either combines with a single name argument (--value belongs to that argument and is refused without it). The batch file is read with every scalar left as text, so Yes, No, On and 12:30 stay the captions they look like instead of becoming booleans and a number, and a value that is not text is refused by the key that holds it. A file that several keys touch is read once and written once regardless of how many of them land in it, and the whole batch is planned before any of it is applied: an invalid key refuses the call before a single file is written, rather than leaving the ones before it already on disk. A translation the call only read - every key it holds already carrying that very text - keeps its bytes and its mtime and stays out of the files it reports. MCP meta_set_localization takes the same entries, dry_run and full_text.

add-field <strings>.yaml строка <key> (meta_add_field with the kind строка) adds the key, and --type carries its text in the default language - the key itself when omitted. The key lands in the element and is echoed into the translations that already exist, with the default-language text, so none of them is left a key short. The note names set-localization, which is what writes the text of a translation. A call on the translation file itself is refused, because that file carries neither a kind nor the sections of an element. The refusal names the element the file belongs to and the same set-localization. It used to read “the kind ? has no section for it”.

add-localization <strings>.yaml <language> adds a translation, and localization-info lists the languages one can be added for. The languages are the platform’s own enumeration, read from the data together with the compatibility mode each one needs, so Chinese, Vietnamese and Tajik stand next to Russian and English once the data carries them. A language a newer mode added is offered and added only for a project of that mode, and a value of LocalizationLanguages the data knows no language by is named in the notes instead of vanishing from the answer. A new translation goes to the folder of the language code with a capital (Localization/Vi); a folder already written in another case (vi) is found as it is, since the platform reads the code regardless of case.

add-dependency attaches a library: it writes the Libraries section of the project descriptor with Name, Vendor and Version. The version here is the library’s release version. A release is issued in the control panel, and a build version with a suffix such as 1.0-42 is rejected. Different versions of one library within a project are not allowed, so attaching a library that is already attached updates the version of the existing entry. What is attached now shows up in project-info under projects[].libraries. The vendor, name, version and the qualified type names of a library come from parsing its archive: elemctl inspect <file.xlib>.

set-access edits AccessControl.Permissions of an object in place, and it knows what each kind allows. --default sets the Default right. --permission Read=PermitEveryone sets an individual one, custom rights of a PrivilegeOnElement included. Either language is taken, and the file gets its own: an English object is written Read: PermitEveryone even when the call spoke Russian. --calc-by fills ComputePermissionsBy, which is mandatory for PermissionsComputedForEachObject. Wrong methods, rights a kind does not have, and per-object rights on a ConstantsSet, a service or a Processing are all rejected: their access settings have no ComputePermissionsBy. The computed-permission handlers stay yours to write, and the notes field says which ones you need. object-info reports the current permissions and the kind’s rights, project-info the Default of every object, both in the Russian names whichever language the file is written in. No section there means the platform falls back to PermitAdmins.

rename-object renames the object’s files, including its forms, the generated ListRow<Name> component of a card list and the WSDL descriptions of a SOAP service client. A description keeps its number, and a reference from one description to another by file name gets the new name. Imported XML schemas (<Name>.Wsdl.<N>.xsd) move, rename and delete with the client too; local location and schemaLocation links follow a rename, while external URLs stay unchanged. The command rewrites references across the whole project and with an eye on the context: the reference-bearing yaml keys Type, Table, DataSource, Form and FormType, the = bindings and the .xbsl code. Attributes, components or dynamic-list fields that merely share the old name are left alone, and so are string literals with UI text. Comments in modules and in yaml get the new name whole, quotes inside a comment included. The translation dictionary is not rewritten as a source: for every comment line the rename changed, its phrases translation is written again under the new line, and the old key stays for any text that still reads it. tokens are left as they are, because the old name may live on in a namesake, and the notes name the pairs: the English spelling of the new name is yours to set. --new-presentation and --old-presentation update the Title and Presentation of the object and its forms. The object’s Id is untouched, so the platform keeps the stored data.

delete-object deletes an object whole: the yaml and module pair, its forms and the generated ListRow<Name> row component with their pairs, and the WSDL descriptions of a SOAP service client. A subsystem is the folder the files live in, so the membership goes away with them. Every remaining mention of the name across the project is listed by file and line, string literals and comments included. A router opening a form by a name in a string, or code seeding data, is exactly the leftover that otherwise surfaces as a runtime error. The command does not edit those mentions: which one is dead code is the author’s call. Deletion is irreversible, so without --apply the command prints the plan. The MCP tool meta_delete_object answers with a plan too, since its dry_run defaults to true.

A rename that only changes letter case, Goods into goods, runs in two steps through a temporary name. A case-insensitive filesystem, Windows or macOS, addresses the old and the new name as one file, and a single-step rename between them is not guaranteed. A failure of the second step undoes the first and leaves no temporary name on disk. On a case-sensitive filesystem the name is free and the rename runs in a single step. The tool renames the files itself, but check the version control system. Git on a case-insensitive filesystem folds ASCII letters only, so a Latin rename goes unnoticed: record it explicitly with git mv <old> <new>. A Cyrillic one is recorded as a delete plus an add, and then every other clone on such a filesystem stops at “untracked working tree files would be overwritten by merge”. There the file under the old name has to be deleted before pulling.

move-object (MCP meta_move_object) moves an object into another folder of its project: a package of the subsystem - a folder that does not exist yet becomes one - another package, the subsystem root or another subsystem. The object takes its forms, modules, list row and list table along, and a SOAP service client its WSDL descriptions. An element of a package lives in the package’s own namespace, so the move repairs what reached the object from its old place: a module or a yaml of another subsystem gets import Subsystem::Package or an Import item, the moved files get the imports of their old subsystem when the move crosses a subsystem boundary, a qualified name - the full Vendor::Project::Subsystem::FormName.ListRowData of a generated form included - is rewritten, and a subsystem that starts importing another one gets it in Using. What needs repairing is decided by the import and visibility rules of the linter, run before and after the move. Within one subsystem the root and the packages see each other, so the move adds no import there, and only the full names follow the files. The command refuses a taken name and a non-public element that another subsystem would reach from now on - the visibility is the author’s decision. An import the move made unnecessary is named in notes, not removed: code/unused-import reports it. rename-package (MCP meta_rename_package) renames a package folder with all its files and rewrites import Subsystem::Batches[::Archive], the Import items and the qualified names across the project. For a Cyrillic package name in a project with a translation dictionary both remind of the pair the name needs.

move-resource (MCP meta_move_resource) moves a resource file or a folder of them into another folder of the same Resources folder; a folder that does not exist yet is created by the move, because an empty folder is kept neither by git nor by a build. rename-resource-folder (MCP meta_rename_resource_folder) renames a folder inside it. A resource is addressed by its path under the folder, so both rewrite the static references that lead to the moved files: the Resource{...} literals of modules and yaml bindings and the bare values of image properties (Image: Pictures/Flag.svg). A key with a namespace names the folder of that subsystem or package; a bare key is looked for in every resources folder of the file’s own subsystem and of the namespaces it imports, and a key two of them hold is named in notes instead of being rewritten. A lookup by a computed string (ResourcesPackage.Current().Get("Folder/...")) is resolved at run time: the commands list such strings by file and line and leave them to the author. delete-resource-folder (MCP meta_delete_resource_folder) deletes a folder with its files and lists both kinds of references without editing them; without --apply it prints the plan. A move into the Resources folder of another subsystem or package is refused: the file would change its namespace, and a lookup by a string at the old place has nothing to rewrite.

resource-references (MCP meta_resource_references) finds the places that name a resource file or a folder and changes nothing. It reads the sources the way a move does, and every place comes with its file, range, line and kind: reference for a static reference, ambiguous for a key held by several namespaces at the winning priority, string for a string with the path, computed for a string with the folder and a computed file name, stem for a whole string that spells the key of a file without its extension. Seed data names a picture by its code this way, and the code adds the extension at run time; the JSON files of the project’s resources are read for it too. The metadata tree of the VS Code extension shows the answer in the References view. Resources of the current subsystem take priority over imported namesakes. The root and packages of that subsystem have equal priority; two matching local namespaces remain ambiguous. --limit (MCP limit, 100 by default) keeps the first places in the list, and total counts all of them. When the limit leaves places out, the answer says hasMore: true, and the command also prints a line on stderr with the --limit that lists them all.

xbsl unused-resources <root> and MCP meta_unused_resources list candidates with no known uses. They scan resource literals, image properties, resource-loading wrappers, JSON keys and links from reached CSS/HTML/SVG/JS files. Unreached resource files do not make their own links reachable. Computed paths and uncertain uses stay separate. --include-protected includes those separate lists; --limit caps each returned list, while summary totals remain complete. Every unused entry is labelled confidence: candidate: external data and arbitrary generated names still need review. The command never deletes files.

Code templates

A template is a short trigger plus a construct. Type the first letters of if, press Ctrl+Space, pick the template, and the whole statement arrives with edit points to tab through. The mechanism mirrors the one in 1C:EDT (the code-templates preference page), file format included.

Templates are offered ahead of the other completions: the construct you are typing out ranks above a name that merely starts the same. They need no Element data, only the LSP server (xbsl.lsp.enabled, on by default). The CLI-index mode of the extension does not offer them.

The builtin set is 51 templates, all in xbsl/templates_builtin.py: the control statements, the declarations (methods with their annotations, structures, enumerations, exception types), queries and the applied idioms - walking a Catalog, register movements, an HttpService handler, per-object access permissions, object events, form handlers. Every pattern is parsed by the same parser the linter runs (tests/test_templates.py), so a template cannot insert code that does not compile.

A pattern holds edit points and choices. The variables use the ${...} syntax of 1C:EDT templates. The platform gives them no English spelling, the way it does for the metadata vocabulary, so the table describes them instead of spelling them out:

Variable Expands to
edit point an edit point; its argument is the pre-selected prompt text
choice a dropdown of the fixed variants listed in its arguments
metadata name a dropdown of this project’s objects of the given kind (Catalog, Enum, …), from the index
qualified metadata name the same, inserted as <Kind>.<Name>

Your own templates live in .xbsl-templates.json at the workspace root; the path comes from --file or from the xbsl.templates.file setting. The file extends the builtin set, and a template with the same name replaces the builtin one. Only what differs from the builtin set is stored, so the next release still reaches you.

xbsl templates list                        # the whole set: builtin plus your own (* marks yours)
xbsl templates export --output my.json     # a dump (to carry your templates to another machine)
xbsl templates import dump.json            # merge a dump into your file

In VS Code the same thing is the XBSL: code templates panel, laid out like the EDT dialog: the list with the call context, the description and the pattern, and buttons to add, edit, delete, import, export and restore the defaults. Saving re-reads the set in the running server, so the next Ctrl+Space already offers the edited template.

Last updated on September 30, 2026

Was this page helpful?